v1.0.178: fix async classify polling — frontend ждёт done>=total через batch-progress, fix путь логов
This commit is contained in:
+29
-2
@@ -1,3 +1,4 @@
|
|||||||
|
// ⛔ НЕ МЕНЯТЬ БЕЗ РАЗРЕШЕНИЯ НАЕЛЯ ⛔
|
||||||
// Contracts App — весь JS на VM (contracts.kube5s.ru)
|
// Contracts App — весь JS на VM (contracts.kube5s.ru)
|
||||||
// Lucee: только домен + index.cfm-скелет
|
// Lucee: только домен + index.cfm-скелет
|
||||||
var VM_API = 'https://contracts.kube5s.ru';
|
var VM_API = 'https://contracts.kube5s.ru';
|
||||||
@@ -140,7 +141,10 @@ async function runClassify() {
|
|||||||
btn.innerHTML = '<span class="spinner"></span> Классификация... ' + Math.round((Date.now() - start) / 1000) + 'с';
|
btn.innerHTML = '<span class="spinner"></span> Классификация... ' + Math.round((Date.now() - start) / 1000) + 'с';
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
// Poll progress каждые 2с
|
var totalFiles = 0;
|
||||||
|
var classifyDone = false;
|
||||||
|
|
||||||
|
// Poll progress каждые 2с — работает и для sync, и для async classify
|
||||||
var pollTimer = setInterval(async function() {
|
var pollTimer = setInterval(async function() {
|
||||||
try {
|
try {
|
||||||
var r = await fetch(VM_API + '/api/batch-progress?batch=' + state.batchId);
|
var r = await fetch(VM_API + '/api/batch-progress?batch=' + state.batchId);
|
||||||
@@ -148,9 +152,16 @@ async function runClassify() {
|
|||||||
if (d.ok && d.counts) {
|
if (d.ok && d.counts) {
|
||||||
var done = (d.counts.classified || 0) + (d.counts.failed || 0);
|
var done = (d.counts.classified || 0) + (d.counts.failed || 0);
|
||||||
var total = d.total || 0;
|
var total = d.total || 0;
|
||||||
|
if (total > 0) totalFiles = total;
|
||||||
btn.innerHTML = '<span class="spinner"></span> Классификация... ' + done + '/' + total + ' (' + Math.round((Date.now() - start) / 1000) + 'с)';
|
btn.innerHTML = '<span class="spinner"></span> Классификация... ' + done + '/' + total + ' (' + Math.round((Date.now() - start) / 1000) + 'с)';
|
||||||
if (done >= total && total > 0) {
|
if (done >= total && total > 0) {
|
||||||
clearInterval(pollTimer);
|
clearInterval(pollTimer);
|
||||||
|
clearInterval(timer);
|
||||||
|
classifyDone = true;
|
||||||
|
btn.innerHTML = '✓ ' + done + '/' + total + ' классифицировано (' + Math.round((Date.now() - start) / 1000) + 'с)';
|
||||||
|
stepDone('stepClassify');
|
||||||
|
stepActive('stepGroups');
|
||||||
|
loadGroupsAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
@@ -163,24 +174,40 @@ async function runClassify() {
|
|||||||
body: JSON.stringify({batch_id: state.batchId})
|
body: JSON.stringify({batch_id: state.batchId})
|
||||||
});
|
});
|
||||||
var data = await resp.json();
|
var data = await resp.json();
|
||||||
|
|
||||||
|
// sync classify: ответ уже содержит done
|
||||||
|
if (data.ok && data.done !== undefined) {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
clearInterval(pollTimer);
|
clearInterval(pollTimer);
|
||||||
if (data.ok) {
|
classifyDone = true;
|
||||||
btn.innerHTML = '✓ ' + data.done + '/' + data.total + ' классифицировано (' + Math.round((Date.now() - start) / 1000) + 'с)';
|
btn.innerHTML = '✓ ' + data.done + '/' + data.total + ' классифицировано (' + Math.round((Date.now() - start) / 1000) + 'с)';
|
||||||
stepDone('stepClassify');
|
stepDone('stepClassify');
|
||||||
stepActive('stepGroups');
|
stepActive('stepGroups');
|
||||||
loadGroupsAction();
|
loadGroupsAction();
|
||||||
|
} else if (data.ok) {
|
||||||
|
// async classify: 202 — ждём poll до done>=total
|
||||||
|
totalFiles = data.total || 0;
|
||||||
|
if (totalFiles === 0) {
|
||||||
|
clearInterval(timer);
|
||||||
|
clearInterval(pollTimer);
|
||||||
|
btn.innerHTML = '✗ нет файлов для классификации';
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
clearInterval(timer);
|
||||||
|
clearInterval(pollTimer);
|
||||||
btn.innerHTML = '✗ ' + (data.error || 'ошибка');
|
btn.innerHTML = '✗ ' + (data.error || 'ошибка');
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
if (!classifyDone) {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
clearInterval(pollTimer);
|
clearInterval(pollTimer);
|
||||||
btn.innerHTML = '✗ ' + e.message;
|
btn.innerHTML = '✗ ' + e.message;
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// loadGroups, buildGroupCard, markGroupDone → groups.js (Фаза 2)
|
// loadGroups, buildGroupCard, markGroupDone → groups.js (Фаза 2)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
# ⛔ НЕ МЕНЯТЬ БЕЗ РАЗРЕШЕНИЯ НАЕЛЯ ⛔
|
||||||
"""Contracts VM server — thin HTTP router. All logic in db/ and services/."""
|
"""Contracts VM server — thin HTTP router. All logic in db/ and services/."""
|
||||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
from socketserver import ThreadingMixIn, TCPServer
|
from socketserver import ThreadingMixIn, TCPServer
|
||||||
@@ -267,7 +268,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
|
|
||||||
# Запустить classify_worker.py в отдельном процессе
|
# Запустить classify_worker.py в отдельном процессе
|
||||||
# Лог в /home/naeel/contracts/logs/ вместо DEVNULL
|
# Лог в /home/naeel/contracts/logs/ вместо DEVNULL
|
||||||
log_dir = "/home/naeel/contracts/logs"
|
log_dir = "/home/naeel/nubes/contracts/logs"
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
log_path = os.path.join(log_dir, f"classify_{batch_id}.log")
|
log_path = os.path.join(log_dir, f"classify_{batch_id}.log")
|
||||||
log_file = open(log_path, "w")
|
log_file = open(log_path, "w")
|
||||||
|
|||||||
Reference in New Issue
Block a user