From c6108ead6d9f37d4455a09efc934e0d52713d3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Fri, 19 Jun 2026 09:40:31 +0400 Subject: [PATCH] =?UTF-8?q?SSE=20streaming:=20=D0=BF=D1=80=D0=BE=D0=B3?= =?UTF-8?q?=D1=80=D0=B5=D1=81=D1=81=20=D0=BF=D0=BE=20=D0=BA=D0=B0=D0=B6?= =?UTF-8?q?=D0=B4=D0=BE=D0=BC=D1=83=20=D1=84=D0=B0=D0=B9=D0=BB=D1=83=20?= =?UTF-8?q?=D0=B2=D0=BE=20=D0=B2=D1=80=D0=B5=D0=BC=D1=8F=20LLM=20(v1.0.53)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.cfm | 95 ++++++---- process.cfm | 495 +++++++++++++++++++++++++++------------------------- 2 files changed, 319 insertions(+), 271 deletions(-) diff --git a/index.cfm b/index.cfm index 542e966..66c7f2f 100644 --- a/index.cfm +++ b/index.cfm @@ -61,7 +61,7 @@
Nubes - Сверка договоров — LLM v1.0.52 — Lucee + Сверка договоров — LLM v1.0.53 — Lucee
@@ -332,7 +332,7 @@ document.getElementById('llmBtn').addEventListener('click', async function() { var diffBody = document.getElementById('diffBody'); var diffStatus = document.getElementById('diffStatus'); diffCard.style.display = 'block'; - diffBody.innerHTML = '⏳ LLM-извлечение...'; + diffBody.innerHTML = ''; diffStatus.textContent = '⏳ 0.0с'; var llmStart = Date.now(); @@ -340,28 +340,37 @@ document.getElementById('llmBtn').addEventListener('click', async function() { diffStatus.textContent = '⏳ ' + ((Date.now() - llmStart) / 1000).toFixed(1) + 'с'; }, 200); - // ОДИН запрос — всё внутри: экстракция + сравнение - try { - var resp = await fetch('/process.cfm?contract_id=' + contractId); - var data = await resp.json(); + // SSE — прогресс по каждому файлу + var es = new EventSource('/process.cfm?contract_id=' + contractId); - if (data.OK) { - // Показать результаты экстракции - var extr = data.EXTRACTED || []; - diffBody.innerHTML = ''; - extr.forEach(function(e) { - if (e.ERROR) { - diffBody.innerHTML += '
✗ ' + (e.FILENAME||e.SUPPLEMENT_ID) + ': ' + e.ERROR + '
'; - } else if (e.SKIPPED) { - diffBody.innerHTML += '
⏭ ' + (e.FILENAME||e.SUPPLEMENT_ID) + ' (уже извлечено, ' + e.ROWS_SAVED + ' строк)
'; - } else { - diffBody.innerHTML += '
✓ ' + (e.FILENAME||e.SUPPLEMENT_ID) + ': ' + e.ROWS_SAVED + ' строк
'; - } - }); + es.onmessage = function(e) { + var d = JSON.parse(e.data); + + if (d.type === 'extract_start') { + diffBody.innerHTML += '
⏳ ' + d.filename + ' → LLM... 0.0с
'; + } + else if (d.type === 'extract_done') { + var el = document.getElementById('ext-' + d.supplement_id); + if (el) el.innerHTML = '✓ ' + d.filename + ': ' + d.count + ' строк (' + d.time_s + 'с)'; + } + else if (d.type === 'extract_skip') { + diffBody.innerHTML += '
⏭ ' + d.filename + ' (уже извлечено, ' + d.count + ' строк)
'; + } + else if (d.type === 'extract_error') { + var el = document.getElementById('ext-' + d.supplement_id); + if (el) el.innerHTML = '✗ ' + d.filename + ': ' + d.error + ''; + else diffBody.innerHTML += '
✗ ' + d.filename + ': ' + d.error + '
'; + } + else if (d.type === 'done') { + clearInterval(timerInterval); + es.close(); + diffStatus.textContent = '✓ ' + d.total_time_s + 'с'; + btn.innerHTML = ' ✓ Готово'; + document.getElementById('chatCard').style.display = 'block'; // Сравнение diffBody.innerHTML += '
📊 Сравнение:
'; - var all = data.ALL_CHANGES || []; + var all = d.all_changes || []; if (all.length === 0) { diffBody.innerHTML += '
Нет допников для сравнения.
'; } else { @@ -369,7 +378,6 @@ document.getElementById('llmBtn').addEventListener('click', async function() { var s = ch.SUMMARY || {}; diffBody.innerHTML += '
📄 Допник (' + ch.TYPE + ')
'; diffBody.innerHTML += '
+' + (s.ADDED||0) + ' ~' + (s.CHANGED||0) + ' -' + (s.DELETED||0) + '
'; - diffStatus.textContent = 'изм:' + (s.CHANGED||0) + ' +' + (s.ADDED||0) + ' -' + (s.DELETED||0); var changes = ch.CHANGES || []; if (changes.length === 0) { @@ -386,19 +394,44 @@ document.getElementById('llmBtn').addEventListener('click', async function() { } }); } - } else { - diffBody.innerHTML = '
✗ ' + (data.ERROR||'Неизвестная ошибка') + '
'; + lucide.createIcons(); } - } catch(e) { - diffBody.innerHTML = '
✗ ' + e.message + '
'; - } + else if (d.type === 'error') { + clearInterval(timerInterval); + es.close(); + diffBody.innerHTML += '
✗ ' + d.message + '
'; + btn.innerHTML = ' Сравнить (LLM)'; + btn.disabled = false; + } + }; - clearInterval(timerInterval); - diffStatus.textContent = '✓ ' + ((Date.now() - llmStart) / 1000).toFixed(1) + 'с'; + es.onerror = function() { + clearInterval(timerInterval); + es.close(); + if (diffBody.innerHTML === '') { + diffBody.innerHTML = '
✗ Ошибка соединения
'; + } + btn.innerHTML = ' Сравнить (LLM)'; + btn.disabled = false; + lucide.createIcons(); + }; - btn.innerHTML = ' ✓ Готово'; - document.getElementById('chatCard').style.display = 'block'; - lucide.createIcons(); + // Таймер для файловых строк + var fileTimerInterval = setInterval(function() { + var now = Date.now(); + document.querySelectorAll('.file-timer').forEach(function(el) { + var start = parseInt(el.dataset.start); + if (start) el.textContent = ((now - start) / 1000).toFixed(1) + 'с'; + }); + }, 200); + + // Сохранить интервалы для очистки + es._fileTimer = fileTimerInterval; + var origClose = es.close; + es.close = function() { + clearInterval(fileTimerInterval); + origClose.call(es); + }; }); function _dc(c, field) { diff --git a/process.cfm b/process.cfm index 75c27cc..c4b05af 100644 --- a/process.cfm +++ b/process.cfm @@ -1,40 +1,50 @@ - + + + + - + +: ok + + - - - - - SELECT s.id as supp_id, s.type, d.id as doc_id, d.filename, d.elements_json, d.parsed_text - FROM supplements s - JOIN documents d ON s.document_id = d.id - WHERE s.contract_id = - AND d.elements_json IS NOT NULL - ORDER BY s.created_at - + data: #serializeJSON({type:"error",message:"contract_id required"})# - - - - - + + + - - - - SELECT prompt_text FROM prompts WHERE contract_id = - - - - + + SELECT s.id as supp_id, s.type, d.id as doc_id, d.filename, d.elements_json + FROM supplements s + JOIN documents d ON s.document_id = d.id + WHERE s.contract_id = + AND d.elements_json IS NOT NULL + ORDER BY s.created_at + - - - - + + - - - + + - - - SELECT row_num, name, price, qty, sum, date_start - FROM spec_rows WHERE supplement_id = - ORDER BY row_num - + + SELECT row_num, name, price, qty, sum, date_start + FROM spec_rows WHERE supplement_id = + ORDER BY row_num + - - - - - - - - - - + + + + + + + data: #serializeJSON({type:"extract_skip",supplement_id:sid,filename:supps.filename,count:ex.recordCount})# - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DELETE FROM spec_rows WHERE supplement_id = - - - - - - - - - - - INSERT INTO spec_rows (supplement_id, row_num, name, price, qty, sum, date_start) - VALUES ( - , - , - , - , - , - , - - ) - + + + + + + + + + + + + + + + + + + + - - - - - + - - + + + + + - - - - + + + data: #serializeJSON({type:"extract_start",supplement_id:sid,filename:supps.filename})# + + + + + + + + + + + + + + + + data: #serializeJSON({type:"extract_error",supplement_id:sid,filename:supps.filename,error:"LLM HTTP #cfhttp.statusCode#",time_s:elapsed})# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM spec_rows WHERE supplement_id = + + + + + + + + + + + INSERT INTO spec_rows (supplement_id, row_num, name, price, qty, sum, date_start) + VALUES ( + , + , + , + , + , + , + + ) + + + + + + + + + + + data: #serializeJSON({type:"extract_done",supplement_id:sid,filename:supps.filename,count:arrayLen(savedRows),time_s:elapsed})# + + + + + data: #serializeJSON({type:"extract_error",supplement_id:sid,filename:supps.filename,error:"JSON parse: " & cfcatch.message,time_s:elapsed})# + + + + + + + + + + + SELECT id FROM supplements WHERE contract_id= AND type='initial' ORDER BY created_at LIMIT 1 + + + + SELECT id FROM supplements WHERE contract_id= ORDER BY created_at LIMIT 1 + + + + + + + + + + + SELECT id, type FROM supplements WHERE contract_id= AND id!= ORDER BY created_at + + + + + + + + + + - - + + + + + - - - SELECT id FROM supplements WHERE contract_id= AND type='initial' ORDER BY created_at LIMIT 1 - - - - SELECT id FROM supplements WHERE contract_id= ORDER BY created_at LIMIT 1 - - - - - - - - - - - SELECT id, type FROM supplements WHERE contract_id= AND id!= ORDER BY created_at - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + - + + + data: #serializeJSON({type:"done",total_time_s:totalTime,all_changes:allChanges})# + + + + + data: #serializeJSON({type:"error",message:cfcatch.message,detail:cfcatch.detail})# + + + -#serializeJSON(result)#