diff --git a/index.cfm b/index.cfm
index 542e966..66c7f2f 100644
--- a/index.cfm
+++ b/index.cfm
@@ -61,7 +61,7 @@
@@ -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)#