From cbdfc7a8c1e5fbf68bcbe4d1af5e41d69fbeb810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Wed, 24 Jun 2026 13:55:22 +0400 Subject: [PATCH] =?UTF-8?q?v1.0.171:=20=E2=96=B6=20=D0=A1=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BD=D0=B8=D1=82=D1=8C=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF?= =?UTF-8?q?=D1=83=20via=20SSE=20+=20classify=20timer/progress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/app.js | 73 ++++++++++++++++++++++++++++++------- deploy/services/grouping.py | 4 +- index.cfm | 2 +- 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/deploy/app.js b/deploy/app.js index 319a9e8..095d321 100644 --- a/deploy/app.js +++ b/deploy/app.js @@ -197,7 +197,28 @@ function showClassifyBtn() { async function runClassify() { var btn = document.getElementById('classifyBtn'); btn.disabled = true; - btn.innerHTML = ' Классификация...'; + btn.innerHTML = ' Классификация... 0с'; + + var start = Date.now(); + var timer = setInterval(function() { + btn.innerHTML = ' Классификация... ' + Math.round((Date.now() - start) / 1000) + 'с'; + }, 1000); + + // Poll progress каждые 2с + var pollTimer = setInterval(async function() { + try { + var r = await fetch(VM_API + '/api/batch-progress?batch=' + batchId); + var d = await r.json(); + if (d.ok && d.counts) { + var done = (d.counts.classified || 0) + (d.counts.failed || 0); + var total = d.total || 0; + btn.innerHTML = ' Классификация... ' + done + '/' + total + ' (' + Math.round((Date.now() - start) / 1000) + 'с)'; + if (done >= total && total > 0) { + clearInterval(pollTimer); + } + } + } catch(e) {} + }, 2000); try { var resp = await fetch(VM_API + '/api/classify-batch', { @@ -206,14 +227,18 @@ async function runClassify() { body: JSON.stringify({batch_id: batchId}) }); var data = await resp.json(); + clearInterval(timer); + clearInterval(pollTimer); if (data.ok) { - btn.innerHTML = '✓ ' + data.done + '/' + data.total + ' классифицировано'; + btn.innerHTML = '✓ ' + data.done + '/' + data.total + ' классифицировано (' + Math.round((Date.now() - start) / 1000) + 'с)'; loadGroups(); } else { btn.innerHTML = '✗ ' + (data.error || 'ошибка'); btn.disabled = false; } } catch(e) { + clearInterval(timer); + clearInterval(pollTimer); btn.innerHTML = '✗ ' + e.message; btn.disabled = false; } @@ -283,27 +308,49 @@ window.runCompareForGroup = async function(gi) { var btn = event.target; btn.disabled = true; - btn.innerHTML = ' Применяю группу...'; + btn.innerHTML = ' Применяю...'; - // 1. Apply groups (creates contracts + supplements) var ar = await fetch(VM_API + '/api/apply-groups', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({batch_id: batchId, groups: [group]}) }); var ad = await ar.json(); - if (!ad.ok) { btn.innerHTML = '✗ ' + ad.error; btn.disabled = false; return; } + if (!ad.ok || !ad.contract_ids || !ad.contract_ids.length) { + btn.innerHTML = '✗ ошибка'; btn.disabled = false; return; + } - // 2. Get the created contract_id from supplements - var sr = await fetch(VM_API + '/api/supplements?contract_id=&batch=' + batchId); - // Actually need contract_id — let's get it from groups data - + var cid = ad.contract_ids[0]; btn.innerHTML = ' Сравнение...'; - // Use EventSource for process-v2 — but we need contract_id - // For now: use the old compare flow with contractId - btn.innerHTML = '✓ Готово — обновите страницу и нажмите «Общее сравнение»'; - btn.disabled = false; + var diffCard = document.getElementById('diffCard'); + var diffBody = document.getElementById('diffBody'); + var diffStatus = document.getElementById('diffStatus'); + diffCard.style.display = 'block'; + diffBody.innerHTML = ''; + diffStatus.textContent = '⏳ 0.0с'; + + var start = Date.now(); + var timer = setInterval(function() { + diffStatus.textContent = '⏳ ' + ((Date.now() - start) / 1000).toFixed(1) + 'с'; + }, 200); + + var es = new EventSource(VM_API + '/process-v2?contract_id=' + cid); + es.onmessage = function(e) { + var d = JSON.parse(e.data); + if (d.type === 'done') { + clearInterval(timer); es.close(); + diffStatus.textContent = '✓ ' + d.total_time_s + 'с'; + btn.innerHTML = '✓ Готово'; btn.disabled = false; + } else if (d.type === 'applied') { + diffBody.innerHTML += '
+' + (d.summary.added||0) + ' ~' + (d.summary.updated||0) + ' -' + (d.summary.deleted||0) + '
'; + } else if (d.type === 'error') { + clearInterval(timer); es.close(); + diffBody.innerHTML += '
✗ ' + d.message + '
'; + btn.innerHTML = '✗'; btn.disabled = false; + } + }; + es.onerror = function() { clearInterval(timer); es.close(); btn.innerHTML = '✗ соединение'; btn.disabled = false; }; }; function uploadFile(file, onProgress) { diff --git a/deploy/services/grouping.py b/deploy/services/grouping.py index c226455..1f82af8 100644 --- a/deploy/services/grouping.py +++ b/deploy/services/grouping.py @@ -108,6 +108,7 @@ def apply_groups(batch_id, groups_data): Возвращает {ok, created: количество созданных supplements}. """ created = 0 + contract_ids = [] for g in groups_data: contract_number = g.get("contract_number", "") if contract_number == "__unresolved__": @@ -118,6 +119,7 @@ def apply_groups(batch_id, groups_data): # Create contract c = db_contracts.insert(contract_number, counterparty) contract_id = c["id"] + contract_ids.append(contract_id) # Create supplements in order for i, d in enumerate(docs): @@ -126,4 +128,4 @@ def apply_groups(batch_id, groups_data): db_supplements.insert(contract_id, doc_id, supp_type) created += 1 - return {"ok": True, "created": created} + return {"ok": True, "created": created, "contract_ids": contract_ids} diff --git a/index.cfm b/index.cfm index 553ff38..4b3d166 100644 --- a/index.cfm +++ b/index.cfm @@ -75,7 +75,7 @@
Nubes - Сверка договоров — LLM AI-driven Event Sourcing v1.0.170 — Lucee + Сверка договоров — LLM AI-driven Event Sourcing v1.0.171 — Lucee