revert: убрал SSE, вернул fetch + добавил skip/DELETE в extractor (v1.0.56)
This commit is contained in:
+21
-4
@@ -20,6 +20,18 @@
|
|||||||
<cfif sup.recordCount EQ 0>
|
<cfif sup.recordCount EQ 0>
|
||||||
<cfset result.error = "supplement not found">
|
<cfset result.error = "supplement not found">
|
||||||
<cfelse>
|
<cfelse>
|
||||||
|
<!--- Проверить: уже извлечено? --->
|
||||||
|
<cfquery name="ex" datasource="baza">
|
||||||
|
SELECT 1 FROM spec_rows WHERE supplement_id = <cfqueryparam value="#url.supplement_id#" cfsqltype="cf_sql_varchar"> LIMIT 1
|
||||||
|
</cfquery>
|
||||||
|
<cfif ex.recordCount GT 0>
|
||||||
|
<cfquery name="cnt" datasource="baza">
|
||||||
|
SELECT COUNT(*) as n FROM spec_rows WHERE supplement_id = <cfqueryparam value="#url.supplement_id#" cfsqltype="cf_sql_varchar">
|
||||||
|
</cfquery>
|
||||||
|
<cfset result = {ok: true, supplement_id: url.supplement_id, rows_saved: cnt.n, skipped: true}>
|
||||||
|
<cfabort>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
<!--- textify: elements → текст --->
|
<!--- textify: elements → текст --->
|
||||||
<cfset elements = deserializeJSON(sup.elements_json)>
|
<cfset elements = deserializeJSON(sup.elements_json)>
|
||||||
<cfset textLines = []>
|
<cfset textLines = []>
|
||||||
@@ -136,11 +148,15 @@
|
|||||||
<cfset unresolved = extracted.unresolved>
|
<cfset unresolved = extracted.unresolved>
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<!--- Сохраняем spec_rows --->
|
<!--- Сохраняем spec_rows (DELETE + INSERT) --->
|
||||||
<cfset savedRows = []>
|
<cfset savedRows = []>
|
||||||
<cfloop array="#rows#" index="row">
|
<cftransaction>
|
||||||
<cftry>
|
<cfquery datasource="baza">
|
||||||
<cfset rn = structKeyExists(row, 'row_num') ? row.row_num : 0>
|
DELETE FROM spec_rows WHERE supplement_id = <cfqueryparam value="#sup.id#" cfsqltype="cf_sql_varchar">
|
||||||
|
</cfquery>
|
||||||
|
<cfloop array="#rows#" index="row">
|
||||||
|
<cftry>
|
||||||
|
<cfset rn = structKeyExists(row, 'row_num') ? row.row_num : 0>
|
||||||
<cfset nm = structKeyExists(row, 'name') ? row.name : ''>
|
<cfset nm = structKeyExists(row, 'name') ? row.name : ''>
|
||||||
<cfset pr = (structKeyExists(row, 'price') AND len(row.price)) ? row.price : ''>
|
<cfset pr = (structKeyExists(row, 'price') AND len(row.price)) ? row.price : ''>
|
||||||
<cfset qt = (structKeyExists(row, 'qty') AND len(row.qty)) ? row.qty : ''>
|
<cfset qt = (structKeyExists(row, 'qty') AND len(row.qty)) ? row.qty : ''>
|
||||||
@@ -165,6 +181,7 @@
|
|||||||
</cfcatch>
|
</cfcatch>
|
||||||
</cftry>
|
</cftry>
|
||||||
</cfloop>
|
</cfloop>
|
||||||
|
</cftransaction>
|
||||||
|
|
||||||
<cfset result = {
|
<cfset result = {
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<img src="/nubes-logo.svg" alt="Nubes">
|
<img src="/nubes-logo.svg" alt="Nubes">
|
||||||
<span class="title">Сверка договоров — LLM <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.55 — Lucee</span></span>
|
<span class="title">Сверка договоров — LLM <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.56 — Lucee</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@@ -142,7 +142,6 @@ lucide.createIcons();
|
|||||||
var UPLOAD_URL = 'https://contracts.kube5s.ru/lucee/upload.cfm';
|
var UPLOAD_URL = 'https://contracts.kube5s.ru/lucee/upload.cfm';
|
||||||
var CONVERT_URL = 'https://contracts.kube5s.ru/convert-doc';
|
var CONVERT_URL = 'https://contracts.kube5s.ru/convert-doc';
|
||||||
var SITE_URL = ''; // same origin for api calls
|
var SITE_URL = ''; // same origin for api calls
|
||||||
var LUCEE_PROXY = 'https://contracts.kube5s.ru/lucee'; // SSE через ВМ (без буферизации)
|
|
||||||
|
|
||||||
var fileInput = document.getElementById('fileInput');
|
var fileInput = document.getElementById('fileInput');
|
||||||
var parseBtn = document.getElementById('parseBtn');
|
var parseBtn = document.getElementById('parseBtn');
|
||||||
@@ -333,7 +332,7 @@ document.getElementById('llmBtn').addEventListener('click', async function() {
|
|||||||
var diffBody = document.getElementById('diffBody');
|
var diffBody = document.getElementById('diffBody');
|
||||||
var diffStatus = document.getElementById('diffStatus');
|
var diffStatus = document.getElementById('diffStatus');
|
||||||
diffCard.style.display = 'block';
|
diffCard.style.display = 'block';
|
||||||
diffBody.innerHTML = '';
|
diffBody.innerHTML = '⏳ LLM-извлечение...';
|
||||||
diffStatus.textContent = '⏳ 0.0с';
|
diffStatus.textContent = '⏳ 0.0с';
|
||||||
|
|
||||||
var llmStart = Date.now();
|
var llmStart = Date.now();
|
||||||
@@ -341,98 +340,65 @@ document.getElementById('llmBtn').addEventListener('click', async function() {
|
|||||||
diffStatus.textContent = '⏳ ' + ((Date.now() - llmStart) / 1000).toFixed(1) + 'с';
|
diffStatus.textContent = '⏳ ' + ((Date.now() - llmStart) / 1000).toFixed(1) + 'с';
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
// SSE — прогресс по каждому файлу
|
// 1. Extract for each supplement
|
||||||
var es = new EventSource(LUCEE_PROXY + '/process.cfm?contract_id=' + contractId);
|
try {
|
||||||
|
var resp = await fetch('/api.cfm?action=query&sql=' + encodeURIComponent("SELECT id, type FROM supplements WHERE contract_id='" + contractId + "' ORDER BY created_at"));
|
||||||
es.onmessage = function(e) {
|
var data = await resp.json();
|
||||||
var d = JSON.parse(e.data);
|
if (data.OK && data.ROWS) {
|
||||||
|
for (var i = 0; i < data.ROWS.length; i++) {
|
||||||
if (d.type === 'extract_start') {
|
try {
|
||||||
diffBody.innerHTML += '<div style="font-size:12px;color:var(--muted);margin-top:2px;" id="ext-' + d.supplement_id + '">⏳ ' + d.filename + ' → LLM... <span class="file-timer" data-start="' + Date.now() + '">0.0с</span></div>';
|
var er = await fetch('/extractor.cfm?supplement_id=' + data.ROWS[i].id);
|
||||||
}
|
var ed = await er.json();
|
||||||
else if (d.type === 'extract_done') {
|
if (ed.OK) {
|
||||||
var el = document.getElementById('ext-' + d.supplement_id);
|
var info = (ed.SKIPPED ? '⏭ ' : '✓ ') + (data.ROWS[i].type || '') + ': ' + ed.ROWS_SAVED + ' строк';
|
||||||
if (el) el.innerHTML = '✓ <span style="color:var(--green);">' + d.filename + ': ' + d.count + ' строк (' + d.time_s + 'с)</span>';
|
diffBody.innerHTML += '<div style="font-size:12px;color:' + (ed.SKIPPED ? 'var(--muted)' : 'var(--green)') + ';">' + info + '</div>';
|
||||||
}
|
|
||||||
else if (d.type === 'extract_skip') {
|
|
||||||
diffBody.innerHTML += '<div style="font-size:12px;color:var(--muted);margin-top:2px;">⏭ ' + d.filename + ' (уже извлечено, ' + d.count + ' строк)</div>';
|
|
||||||
}
|
|
||||||
else if (d.type === 'extract_error') {
|
|
||||||
var el = document.getElementById('ext-' + d.supplement_id);
|
|
||||||
if (el) el.innerHTML = '✗ <span style="color:var(--destructive);">' + d.filename + ': ' + d.error + '</span>';
|
|
||||||
else diffBody.innerHTML += '<div style="font-size:12px;color:var(--destructive);margin-top:2px;">✗ ' + d.filename + ': ' + d.error + '</div>';
|
|
||||||
}
|
|
||||||
else if (d.type === 'done') {
|
|
||||||
clearInterval(timerInterval);
|
|
||||||
es.close();
|
|
||||||
diffStatus.textContent = '✓ ' + d.total_time_s + 'с';
|
|
||||||
btn.innerHTML = '<i data-lucide="check" style="width:16px;height:16px;"></i> ✓ Готово';
|
|
||||||
document.getElementById('chatCard').style.display = 'block';
|
|
||||||
|
|
||||||
// Сравнение
|
|
||||||
diffBody.innerHTML += '<div style="margin-top:8px;font-weight:600;">📊 Сравнение:</div>';
|
|
||||||
var all = d.all_changes || [];
|
|
||||||
if (all.length === 0) {
|
|
||||||
diffBody.innerHTML += '<div style="color:var(--muted);font-size:12px;">Нет допников для сравнения.</div>';
|
|
||||||
} else {
|
|
||||||
all.forEach(function(ch) {
|
|
||||||
var s = ch.SUMMARY || {};
|
|
||||||
diffBody.innerHTML += '<div style="font-weight:600;margin-top:12px;">📄 Допник (' + ch.TYPE + ')</div>';
|
|
||||||
diffBody.innerHTML += '<div style="font-size:12px;margin-left:8px;"><span style="color:var(--green);">+' + (s.ADDED||0) + '</span> <span style="color:#eab308;">~' + (s.CHANGED||0) + '</span> <span style="color:var(--destructive);">-' + (s.DELETED||0) + '</span></div>';
|
|
||||||
|
|
||||||
var changes = ch.CHANGES || [];
|
|
||||||
if (changes.length === 0) {
|
|
||||||
diffBody.innerHTML += '<div style="color:var(--muted);font-size:12px;">Нет изменений</div>';
|
|
||||||
} else {
|
} else {
|
||||||
var tbl = '<div class="table-wrap" style="margin-top:4px;"><table><thead><tr><th>№</th><th>Услуга</th><th>Цена</th><th>Объём</th><th>Сумма</th><th>Начало</th></tr></thead><tbody>';
|
diffBody.innerHTML += '<div style="font-size:12px;color:var(--destructive);">✗ ' + (ed.ERROR||'') + '</div>';
|
||||||
changes.forEach(function(c) {
|
|
||||||
var cls = c.CHANGE_TYPE === 'added' ? 'diff-added' : c.CHANGE_TYPE === 'deleted' ? 'diff-deleted' : c.CHANGE_TYPE === 'changed' ? 'diff-changed' : '';
|
|
||||||
var vals = c.NEW_VALUES && Object.keys(c.NEW_VALUES).length > 0 ? c.NEW_VALUES : (c.OLD_VALUES || {});
|
|
||||||
tbl += '<tr class="' + cls + '"><td>' + (c.ROW_NUM||'') + '</td><td>' + (vals.NAME||'') + '</td><td class="num-cell">' + _dc(c,'PRICE') + '</td><td class="num-cell">' + _dc(c,'QTY') + '</td><td class="num-cell">' + _dc(c,'SUM') + '</td><td>' + (vals.DATE_START||'') + '</td></tr>';
|
|
||||||
});
|
|
||||||
tbl += '</tbody></table></div>';
|
|
||||||
diffBody.innerHTML += tbl;
|
|
||||||
}
|
}
|
||||||
});
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
lucide.createIcons();
|
|
||||||
}
|
}
|
||||||
else if (d.type === 'error') {
|
} catch(e) {}
|
||||||
clearInterval(timerInterval);
|
|
||||||
es.close();
|
// 2. Differ
|
||||||
diffBody.innerHTML += '<div style="color:var(--destructive);margin-top:8px;">✗ ' + d.message + '</div>';
|
diffBody.innerHTML += '<div style="margin-top:8px;font-weight:600;">📊 Сравнение:</div>';
|
||||||
btn.innerHTML = '<i data-lucide="scale" style="width:16px;height:16px;"></i> Сравнить (LLM)';
|
try {
|
||||||
btn.disabled = false;
|
var resp = await fetch('/differ.cfm?contract_id=' + contractId);
|
||||||
|
var data = await resp.json();
|
||||||
|
if (data.OK && data.ALL_CHANGES) {
|
||||||
|
var all = data.ALL_CHANGES;
|
||||||
|
diffBody.innerHTML = '';
|
||||||
|
all.forEach(function(ch) {
|
||||||
|
var s = ch.SUMMARY || {};
|
||||||
|
diffBody.innerHTML += '<div style="font-weight:600;margin-top:12px;">📄 Допник (' + ch.TYPE + ')</div>';
|
||||||
|
diffBody.innerHTML += '<div style="font-size:12px;margin-left:8px;"><span style="color:var(--green);">+' + (s.ADDED||0) + '</span> <span style="color:#eab308;">~' + (s.CHANGED||0) + '</span> <span style="color:var(--destructive);">-' + (s.DELETED||0) + '</span></div>';
|
||||||
|
diffStatus.textContent = 'изм:' + (s.CHANGED||0) + ' +' + (s.ADDED||0) + ' -' + (s.DELETED||0);
|
||||||
|
|
||||||
|
var changes = ch.CHANGES || [];
|
||||||
|
if (changes.length === 0) {
|
||||||
|
diffBody.innerHTML += '<div style="color:var(--muted);font-size:12px;">Нет изменений</div>';
|
||||||
|
} else {
|
||||||
|
var tbl = '<div class="table-wrap" style="margin-top:4px;"><table><thead><tr><th>№</th><th>Услуга</th><th>Цена</th><th>Объём</th><th>Сумма</th><th>Начало</th></tr></thead><tbody>';
|
||||||
|
changes.forEach(function(c) {
|
||||||
|
var cls = c.CHANGE_TYPE === 'added' ? 'diff-added' : c.CHANGE_TYPE === 'deleted' ? 'diff-deleted' : c.CHANGE_TYPE === 'changed' ? 'diff-changed' : '';
|
||||||
|
var vals = c.NEW_VALUES && Object.keys(c.NEW_VALUES).length > 0 ? c.NEW_VALUES : (c.OLD_VALUES || {});
|
||||||
|
tbl += '<tr class="' + cls + '"><td>' + (c.ROW_NUM||'') + '</td><td>' + (vals.NAME||'') + '</td><td class="num-cell">' + _dc(c,'PRICE') + '</td><td class="num-cell">' + _dc(c,'QTY') + '</td><td class="num-cell">' + _dc(c,'SUM') + '</td><td>' + (vals.DATE_START||'') + '</td></tr>';
|
||||||
|
});
|
||||||
|
tbl += '</tbody></table></div>';
|
||||||
|
diffBody.innerHTML += tbl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
diffBody.innerHTML += '<div style="color:var(--destructive);">✗ ' + (data.ERROR||'') + '</div>';
|
||||||
}
|
}
|
||||||
};
|
} catch(e) { diffBody.innerHTML += '<div style="color:var(--destructive);">✗ ' + e.message + '</div>'; }
|
||||||
|
|
||||||
es.onerror = function() {
|
clearInterval(timerInterval);
|
||||||
clearInterval(timerInterval);
|
diffStatus.textContent = '✓ ' + ((Date.now() - llmStart) / 1000).toFixed(1) + 'с';
|
||||||
es.close();
|
|
||||||
if (diffBody.innerHTML === '') {
|
|
||||||
diffBody.innerHTML = '<div style="color:var(--destructive);">✗ Ошибка соединения</div>';
|
|
||||||
}
|
|
||||||
btn.innerHTML = '<i data-lucide="scale" style="width:16px;height:16px;"></i> Сравнить (LLM)';
|
|
||||||
btn.disabled = false;
|
|
||||||
lucide.createIcons();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Таймер для файловых строк
|
btn.innerHTML = '<i data-lucide="check" style="width:16px;height:16px;"></i> ✓ Готово';
|
||||||
var fileTimerInterval = setInterval(function() {
|
document.getElementById('chatCard').style.display = 'block';
|
||||||
var now = Date.now();
|
lucide.createIcons();
|
||||||
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) {
|
function _dc(c, field) {
|
||||||
|
|||||||
Reference in New Issue
Block a user