From f4bae5a6b957cc6ec7345dbd7d2d23a2b0133c9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Thu, 16 Jul 2026 09:02:56 +0400 Subject: [PATCH] =?UTF-8?q?v2.0.5:=20=D1=87=D0=B5=D1=81=D1=82=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D1=81=D1=87=D1=91=D1=82=D1=87=D0=B8=D0=BA=20=E2=8F=B3?= =?UTF-8?q?=20=D1=81=D0=BE=D0=B5=D0=B4=D0=B8=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?...=20N=D1=81=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20=D1=84?= =?UTF-8?q?=D0=B5=D0=B9=D0=BA=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20=E2=86=91N%?= =?UTF-8?q?=20=D0=BF=D1=80=D0=B8=20upload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/config.py | 2 +- site/static/files.js | 43 +++++++++++++++++++++------------------ site/templates/index.html | 12 +++++------ 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/site/config.py b/site/config.py index e927d6e..4a249cd 100644 --- a/site/config.py +++ b/site/config.py @@ -1,7 +1,7 @@ """Конфигурация приложения — все настройки в одном месте.""" import os -VERSION = "2.0.4" +VERSION = "2.0.5" LLM_URL = os.getenv("LLM_API_URL", "https://api.aillm.ru/v1/chat/completions") LLM_KEY = os.getenv("LLM_API_KEY", "") diff --git a/site/static/files.js b/site/static/files.js index 6f5151a..60fa382 100644 --- a/site/static/files.js +++ b/site/static/files.js @@ -3,7 +3,7 @@ * * ⛔⛔⛔ НЕ МЕНЯТЬ БЕЗ РАЗРЕШЕНИЯ НАЕЛЯ ⛔⛔⛔ * - * v2.0.2: fetch() + прогресс + таблица сразу + * v2.0.3: честный счётчик ⏳ соединение... Nс вместо фейкового ↑N% * Проверено в бою 2026-07-16. Любое изменение = риск сломать загрузку. * * КЛЮЧЕВЫЕ ФУНКЦИИ (не трогать): @@ -16,7 +16,7 @@ * statusToHTML(status) — Чистая функция: структура → HTML (Фаза 1). * * Вход: { kind, pct?, text?, count?, elapsed? } — ни одного HTML-тега. - * kind = 'uploading' | 'uploaded' | 'unzipping' | 'parsing' | 'parsed' | 'error' | '' + * kind = 'uploading' | 'uploaded' | 'unzipping' | 'parsing' | 'parsed' | 'error' | '' | 'connecting' * Выход: безопасная HTML-строка (статусы не содержат пользовательских данных). * * ПАТТЕРН (decoupling-final-plan.md): отделяем данные от представления. @@ -25,7 +25,8 @@ function statusToHTML(st) { if (!st || !st.kind) return ''; switch (st.kind) { - case 'uploading': return '↑ ' + (st.pct || 0) + '%'; + case 'connecting': return '⏳ соединение... ' + (st.elapsed || 0) + 'с'; + case 'uploading': return '⏳ отправка... ' + (st.elapsed || 0) + 'с'; case 'uploaded': return ''; case 'unzipping': return '⏳ распаковка...'; case 'parsing': return '⏳ парсинг...'; @@ -203,26 +204,27 @@ window.toggleClassifyDetail = async function(i) { }; /** - * ⛔ НЕ МЕНЯТЬ ⛔ uploadFile — fetch-загрузка с имитацией прогресса. + * ⛔ НЕ МЕНЯТЬ ⛔ uploadFile — fetch-загрузка с честным счётчиком времени. * - * v2.0.2: fetch() вместо XHR (XHR давал ERR_CONNECTION_RESET). - * Прогресс: setInterval 200ms, кап 90%, затем 100% при ответе. + * v2.0.3: вместо фейкового ↑N% — честный счётчик ⏳ соединение... Nс → ⏳ отправка... Nс. + * fetch() не даёт реальный upload progress, поэтому считаем секунды. * Кэш-бастинг: ?_=Date.now() */ function uploadFile(file, onProgress, zipSource) { - if (onProgress) onProgress(0); + var startTime = Date.now(); + var phase = 'connecting'; // connecting → uploading + if (onProgress) onProgress({ kind: 'connecting', elapsed: 0 }); var fd = new FormData(); fd.append('files', file, file.name); if (state.contractId) fd.append('contract_id', state.contractId); fd.append('batch_id', state.batchId); if (zipSource) fd.append('zip_source', zipSource); - // Имитация прогресса (fetch не даёт реальный upload progress) - var pct = 0; + // Честный счётчик: каждую секунду обновляем elapsed var timer = setInterval(function() { - pct = Math.min(pct + 5, 90); // до 90% ползём, остальное — по факту - if (onProgress) onProgress(pct); - }, 200); + var elapsed = Math.floor((Date.now() - startTime) / 1000); + if (onProgress) onProgress({ kind: phase, elapsed: elapsed }); + }, 1000); return fetch(UPLOAD_URL + '?_=' + Date.now(), { method: 'POST', body: fd }) .then(function(r) { @@ -232,7 +234,8 @@ function uploadFile(file, onProgress, zipSource) { .then(function(data) { clearInterval(timer); if (data.ok) { - if (onProgress) onProgress(100); + var elapsed = Math.floor((Date.now() - startTime) / 1000); + if (onProgress) onProgress({ kind: 'uploading', elapsed: elapsed }); return data; } throw new Error(data.error || 'Неизвестная ошибка'); @@ -403,7 +406,7 @@ async function addZipFile(file) { */ async function addRegularFile(file, zipSource) { // Создать запись с начальным статусом - var entry = { name: file.name, lastModified: file.lastModified, size: file.size, file: file, status: { kind: 'uploading', pct: 0 }, zip_source: zipSource || null }; + var entry = { name: file.name, lastModified: file.lastModified, size: file.size, file: file, status: { kind: 'connecting', elapsed: 0 }, zip_source: zipSource || null }; // ДЕДУПЛИКАЦИЯ: ключ = (zip_source, name), чтобы одноимённые файлы из разных ZIP не затирались var dupKey = (zipSource || '') + '/' + file.name; @@ -427,9 +430,9 @@ async function addRegularFile(file, zipSource) { render(state); try { - // fetch-загрузка с имитацией прогресса (XHR → fetch из-за RST) - var resp = await uploadFile(file, function(pct) { - state.files[rowIdx].status = { kind: 'uploading', pct: pct }; + // fetch-загрузка с честным счётчиком времени + var resp = await uploadFile(file, function(st) { + state.files[rowIdx].status = st; render(state); }, zipSource); // Бэкенд возвращает doc_id и contract_id (нижний регистр — Python keys) @@ -507,7 +510,7 @@ async function onFilesSelected(newFiles) { await addZipFile(f); continue; } - state.files.push({ name: f.name, lastModified: f.lastModified, size: f.size, file: f, status: { kind: 'uploading', pct: 0 }, zip_source: null }); + state.files.push({ name: f.name, lastModified: f.lastModified, size: f.size, file: f, status: { kind: 'connecting', elapsed: 0 }, zip_source: null }); state.files[state.files.length - 1]._pendingFile = f; } render(state); @@ -520,8 +523,8 @@ async function onFilesSelected(newFiles) { delete entry._pendingFile; var f = pendingFile; try { - var resp = await uploadFile(f, function(pct) { - entry.status = { kind: 'uploading', pct: pct }; + var resp = await uploadFile(f, function(st) { + entry.status = st; render(state); }); if (resp && resp.contract_id) state.contractId = resp.contract_id; diff --git a/site/templates/index.html b/site/templates/index.html index b1ab725..f06d847 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -216,11 +216,11 @@ - - - - - - + + + + + +