v2.0.5: честный счётчик ⏳ соединение... Nс вместо фейкового ↑N% при upload
Deploy contracts-flask / validate (push) Successful in 0s
Deploy contracts-flask / validate (push) Successful in 0s
This commit is contained in:
+1
-1
@@ -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", "")
|
||||
|
||||
+23
-20
@@ -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 '<span class="status-ok">✓</span>';
|
||||
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;
|
||||
|
||||
@@ -216,11 +216,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/state.js?v=2.0.4"></script>
|
||||
<script src="/static/app_utils.js?v=2.0.4"></script>
|
||||
<script src="/static/files.js?v=2.0.4"></script>
|
||||
<script src="/static/groups.js?v=2.0.4"></script>
|
||||
<script src="/static/compare.js?v=2.0.4"></script>
|
||||
<script src="/static/app.js?v=2.0.4"></script>
|
||||
<script src="/static/state.js?v=2.0.5"></script>
|
||||
<script src="/static/app_utils.js?v=2.0.5"></script>
|
||||
<script src="/static/files.js?v=2.0.5"></script>
|
||||
<script src="/static/groups.js?v=2.0.5"></script>
|
||||
<script src="/static/compare.js?v=2.0.5"></script>
|
||||
<script src="/static/app.js?v=2.0.5"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user