v3.2.0: LLM-извлечение + сравнение допников — кнопка Сравнить
This commit is contained in:
+155
-1
@@ -54,12 +54,19 @@
|
||||
.modal-body .kv .k { color: var(--muted); width: 110px; flex-shrink: 0; }
|
||||
.modal-body .kv .v { word-break: break-all; }
|
||||
.modal-body pre { background: var(--brand-grey-light); padding: 10px; border-radius: 6px; font-size: 12px; max-height: 200px; overflow-y: auto; white-space: pre-wrap; margin-top: 4px; }
|
||||
.diff-added { background: rgba(34,197,94,.1); }
|
||||
.diff-deleted { background: rgba(239,68,68,.1); text-decoration: line-through; }
|
||||
.diff-changed { background: rgba(234,179,8,.1); }
|
||||
.diff-added td { color: var(--green); }
|
||||
.diff-deleted td { color: var(--destructive); }
|
||||
.diff-field-old { color: var(--destructive); text-decoration: line-through; font-size: 11px; }
|
||||
.diff-field-new { color: var(--green); font-weight: 600; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
<img src="{{ url_for('static', filename='nubes-logo.svg') }}" alt="Nubes">
|
||||
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v3.1.1</span></span>
|
||||
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v3.2.0</span></span>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
@@ -83,8 +90,21 @@
|
||||
<button class="btn btn-primary" id="parseBtn" style="width:100%;justify-content:center;margin-top:12px;" disabled>
|
||||
<i data-lucide="play" style="width:16px;height:16px;"></i> Парсинг
|
||||
</button>
|
||||
|
||||
<button class="btn btn-primary" id="llmBtn" style="width:100%;justify-content:center;margin-top:8px;display:none;">
|
||||
<i data-lucide="scale" style="width:16px;height:16px;"></i> Сравнить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Результаты сравнения -->
|
||||
<div class="card" id="diffCard" style="display:none;">
|
||||
<div class="card-header">
|
||||
<i data-lucide="file-diff" style="width:18px;height:18px;"></i>
|
||||
Результаты сравнения <span id="diffStatus" style="font-weight:400;font-size:12px;margin-left:8px;"></span>
|
||||
</div>
|
||||
<div class="card-body" id="diffBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="modalOverlay" onclick="closeModal(event)">
|
||||
@@ -318,6 +338,12 @@
|
||||
es.close();
|
||||
parseBtn.disabled = true;
|
||||
parseBtn.innerHTML = '<i data-lucide="check" style="width:16px;height:16px;"></i> ✓ Готово';
|
||||
|
||||
// Показать кнопку «Сравнить»
|
||||
var llmBtn = document.getElementById('llmBtn');
|
||||
llmBtn.style.display = 'block';
|
||||
llmBtn.disabled = false;
|
||||
llmBtn.innerHTML = '<i data-lucide="scale" style="width:16px;height:16px;"></i> Сравнить';
|
||||
lucide.createIcons();
|
||||
|
||||
var summaryRow = document.createElement('tr');
|
||||
@@ -410,6 +436,134 @@
|
||||
window.showInfo = showInfo;
|
||||
window.closeModal = closeModal;
|
||||
|
||||
// ── LLM: Сравнить ────────────────────────────────────────────
|
||||
|
||||
document.getElementById('llmBtn').addEventListener('click', function() {
|
||||
if (!contractId) return;
|
||||
|
||||
var llmBtn = document.getElementById('llmBtn');
|
||||
llmBtn.disabled = true;
|
||||
llmBtn.innerHTML = '<span style="display:inline-block;width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;"></span> Анализ...';
|
||||
|
||||
var diffCard = document.getElementById('diffCard');
|
||||
var diffBody = document.getElementById('diffBody');
|
||||
var diffStatus = document.getElementById('diffStatus');
|
||||
diffCard.style.display = 'block';
|
||||
diffBody.innerHTML = '<span style="color:var(--muted);">⏳ LLM-извлечение...</span>';
|
||||
|
||||
var es = new EventSource('/llm/process/' + contractId);
|
||||
var filesDone = 0;
|
||||
|
||||
es.onmessage = function(e) {
|
||||
var d = JSON.parse(e.data);
|
||||
|
||||
if (d.type === 'extract_start') {
|
||||
diffBody.innerHTML += '<div style="font-size:12px;color:var(--muted);">🔍 ' + d.name + ' — извлечение...</div>';
|
||||
}
|
||||
|
||||
else if (d.type === 'extract_done') {
|
||||
filesDone++;
|
||||
diffStatus.textContent = '✓ ' + filesDone + ' файлов, ' + d.count + ' строк';
|
||||
diffBody.innerHTML += '<div style="font-size:12px;color:var(--green);">✓ ' + d.name + ' — ' + d.count + ' строк (' + d.time_s + 'с)</div>';
|
||||
}
|
||||
|
||||
else if (d.type === 'extract_skip') {
|
||||
filesDone++;
|
||||
diffBody.innerHTML += '<div style="font-size:12px;color:var(--muted);">⏭ ' + d.name + ' — уже извлечено (' + d.count + ' строк)</div>';
|
||||
}
|
||||
|
||||
else if (d.type === 'extract_error') {
|
||||
diffBody.innerHTML += '<div style="font-size:12px;color:var(--destructive);">✗ ' + d.name + ': ' + d.error + '</div>';
|
||||
}
|
||||
|
||||
else if (d.type === 'diff_start') {
|
||||
diffBody.innerHTML += '<div style="margin-top:8px;font-weight:600;">📊 Сравнение допников:</div>';
|
||||
}
|
||||
|
||||
else if (d.type === 'diff_file') {
|
||||
var added = d.summary ? (d.summary.added || 0) : 0;
|
||||
var changed = d.summary ? (d.summary.changed || 0) : 0;
|
||||
var deleted = d.summary ? (d.summary.deleted || 0) : 0;
|
||||
diffBody.innerHTML += '<div style="font-size:12px;"> ' + d.name + ': <span style="color:var(--green);">+' + added + '</span> <span style="color:#eab308;">~' + changed + '</span> <span style="color:var(--destructive);">-' + deleted + '</span></div>';
|
||||
}
|
||||
|
||||
else if (d.type === 'done') {
|
||||
es.close();
|
||||
llmBtn.innerHTML = '<i data-lucide="check" style="width:16px;height:16px;"></i> ✓ Готово';
|
||||
lucide.createIcons();
|
||||
diffStatus.textContent = '✓ ' + d.total_time_s + 'с';
|
||||
|
||||
// Построить таблицу изменений
|
||||
var all = d.all_changes || [];
|
||||
if (all.length === 0) {
|
||||
diffBody.innerHTML += '<div style="color:var(--muted);margin-top:8px;">Нет допников для сравнения.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
all.forEach(function(ch) {
|
||||
diffBody.innerHTML += '<div style="font-weight:600;margin-top:12px;">📄 ' + ch.filename + ' (' + ch.type + ')</div>';
|
||||
|
||||
if (!ch.changes || ch.changes.length === 0) {
|
||||
diffBody.innerHTML += '<div style="color:var(--muted);font-size:12px;">Нет изменений</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
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>';
|
||||
|
||||
ch.changes.forEach(function(c) {
|
||||
var cls = '';
|
||||
if (c.change_type === 'added') cls = 'diff-added';
|
||||
else if (c.change_type === 'deleted') cls = 'diff-deleted';
|
||||
else if (c.change_type === 'changed') cls = 'diff-changed';
|
||||
|
||||
var vals = c.new_values || c.old_values || {};
|
||||
|
||||
var price = _diffCell(c, 'price');
|
||||
var qty = _diffCell(c, 'qty');
|
||||
var sum = _diffCell(c, 'sum');
|
||||
|
||||
tbl += '<tr class="' + cls + '">' +
|
||||
'<td>' + (c.row_num || '') + '</td>' +
|
||||
'<td>' + (vals.name || '') + '</td>' +
|
||||
'<td class="num-cell">' + price + '</td>' +
|
||||
'<td class="num-cell">' + qty + '</td>' +
|
||||
'<td class="num-cell">' + sum + '</td>' +
|
||||
'<td>' + (vals.date_start || '') + '</td>' +
|
||||
'</tr>';
|
||||
});
|
||||
|
||||
tbl += '</tbody></table></div>';
|
||||
diffBody.innerHTML += tbl;
|
||||
});
|
||||
}
|
||||
|
||||
else if (d.type === 'error') {
|
||||
diffBody.innerHTML += '<div style="color:var(--destructive);margin-top:8px;">✗ ' + d.message + '</div>';
|
||||
}
|
||||
};
|
||||
|
||||
es.addEventListener('error', function() {
|
||||
es.close();
|
||||
llmBtn.innerHTML = '<i data-lucide="scale" style="width:16px;height:16px;"></i> Сравнить';
|
||||
llmBtn.disabled = false;
|
||||
lucide.createIcons();
|
||||
});
|
||||
});
|
||||
|
||||
function _diffCell(c, field) {
|
||||
if (c.change_type === 'changed' && c.old_values && c.new_values) {
|
||||
var ov = c.old_values[field];
|
||||
var nv = c.new_values[field];
|
||||
if (ov !== nv && ov !== undefined) {
|
||||
return '<span class="diff-field-old">' + (ov !== null ? ov : '—') + '</span> → <span class="diff-field-new">' + (nv !== null ? nv : '—') + '</span>';
|
||||
}
|
||||
}
|
||||
var vals = c.new_values || c.old_values || {};
|
||||
var v = vals[field];
|
||||
return v !== null && v !== undefined ? v : '—';
|
||||
}
|
||||
|
||||
var style = document.createElement('style');
|
||||
style.textContent = '@keyframes spin { to { transform: rotate(360deg); } }';
|
||||
document.head.appendChild(style);
|
||||
|
||||
Reference in New Issue
Block a user