v1.0.170: UI — Общее сравнение, Классификация и группы, пояснения для нераспознанных

This commit is contained in:
2026-06-24 13:17:50 +04:00
parent abba9c4f1d
commit d848caacb2
2 changed files with 66 additions and 18 deletions
+63 -15
View File
@@ -229,33 +229,81 @@ async function loadGroups() {
diffCard.style.display = 'block';
diffBody.innerHTML = '<div style="font-weight:600;margin-bottom:8px;">📋 Найдено групп: ' + data.groups.length + '</div>';
// Сохраняем группы для compare
window._groupsData = data.groups;
data.groups.forEach(function(g, gi) {
var isUnresolved = g.contract_number === '__unresolved__';
var card = document.createElement('div');
card.style.cssText = 'border:1px solid ' + (isUnresolved ? 'var(--destructive)' : 'var(--brand-gray)') + ';border-radius:6px;padding:10px;margin-bottom:8px;';
card.innerHTML = '<div style="font-weight:600;margin-bottom:6px;">' +
(isUnresolved ? '❓ Не распознано' : '📄 ' + (g.contract_number || 'Без номера') + ' — ' + (g.counterparty || 'контрагент не определён')) +
'</div>' +
'<div style="font-size:12px;">' +
g.documents.map(function(d) {
if (isUnresolved) {
var docsHtml = g.documents.map(function(d) {
var reason = '';
if (d.parent_number) {
reason = ' — нет базового договора №' + d.parent_number;
} else if (d.classify_status === 'failed') {
reason = ' — ошибка классификации: ' + (d.error_message || '?');
} else if (!d.doc_type || d.doc_type === 'other') {
reason = ' — тип не определён';
} else {
reason = ' — нет matching-договора';
}
return '<div style="padding:2px 0;">' +
'<span style="color:var(--muted);">[' + (d.doc_type || '?') + ']</span> ' +
d.filename +
(d.doc_date ? ' <span style="color:var(--muted);">' + d.doc_date + '</span>' : '') +
'<span style="color:var(--destructive);font-size:11px;">' + reason + '</span>' +
'</div>';
}).join('') +
'</div>';
if (!isUnresolved) {
card.innerHTML += '<button class="btn" style="margin-top:6px;font-size:12px;height:28px;" onclick="runCompareForGroup(\'' + gi + '\')">▶ Сравнить</button>';
}).join('');
card.innerHTML = '<div style="font-weight:600;margin-bottom:6px;">❓ Не распознано (' + g.documents.length + ' файлов)</div>' +
'<div style="font-size:12px;">' + docsHtml + '</div>' +
'<div style="font-size:11px;color:var(--muted);margin-top:4px;">Добавьте базовые договоры для этих номеров или перетащите файлы в группу вручную.</div>';
} else {
card.innerHTML = '<div style="font-weight:600;margin-bottom:6px;">📄 Договор №' + (g.contract_number || '?') + ' — ' + (g.counterparty || 'контрагент не определён') + '</div>' +
'<div style="font-size:12px;">' +
g.documents.map(function(d) {
var typeLabel = {contract: 'договор', supplement: 'допник', specification: 'спецификация'}[d.doc_type] || d.doc_type;
return '<div style="padding:2px 0;">' +
'<span style="color:var(--muted);">' + (typeLabel || '?') + '</span> ' +
d.filename +
(d.doc_date ? ' <span style="color:var(--muted);">' + d.doc_date + '</span>' : '') +
'</div>';
}).join('') + '</div>';
card.innerHTML += '<button class="btn btn-primary" style="margin-top:6px;font-size:12px;height:28px;" onclick="runCompareForGroup(' + gi + ')">▶ Сравнить эту группу</button>';
}
diffBody.appendChild(card);
});
lucide.createIcons();
}
window.runCompareForGroup = function(gi) {
// TODO: run /process-v2 for the specific contract
alert('Сравнение для группы ' + gi + ' — в разработке');
window.runCompareForGroup = async function(gi) {
var group = window._groupsData[gi];
if (!group || group.contract_number === '__unresolved__') return;
var btn = event.target;
btn.disabled = true;
btn.innerHTML = '<span class="spinner"></span> Применяю группу...';
// 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; }
// 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
btn.innerHTML = '<span class="spinner"></span> Сравнение...';
// Use EventSource for process-v2 — but we need contract_id
// For now: use the old compare flow with contractId
btn.innerHTML = '✓ Готово — обновите страницу и нажмите «Общее сравнение»';
btn.disabled = false;
};
function uploadFile(file, onProgress) {
@@ -309,7 +357,7 @@ function uploadFile(file, onProgress) {
});
}
// ── LLM: Сравнить (Event Sourcing) ────────────────────────
// ── LLM: Общее сравнение (Event Sourcing) ────────────────────────
document.getElementById('llmBtn').addEventListener('click', function() {
if (!contractId) return;
var btn = this;
@@ -394,7 +442,7 @@ document.getElementById('llmBtn').addEventListener('click', function() {
clearInterval(timerInterval);
es.close();
diffBody.innerHTML += '<div style="color:var(--destructive);margin-top:8px;">✗ ' + d.message + '</div>';
btn.innerHTML = '<i data-lucide="scale" style="width:16px;height:16px;"></i> Сравнить';
btn.innerHTML = '<i data-lucide="scale" style="width:16px;height:16px;"></i> Общее сравнение';
btn.disabled = false;
}
};
@@ -405,7 +453,7 @@ document.getElementById('llmBtn').addEventListener('click', function() {
if (diffBody.innerHTML === '') {
diffBody.innerHTML = '<div style="color:var(--destructive);">✗ Ошибка соединения</div>';
}
btn.innerHTML = '<i data-lucide="scale" style="width:16px;height:16px;"></i> Сравнить';
btn.innerHTML = '<i data-lucide="scale" style="width:16px;height:16px;"></i> Общее сравнение';
btn.disabled = false;
lucide.createIcons();
};
+3 -3
View File
@@ -75,7 +75,7 @@
<body>
<div class="topbar">
<img src="/nubes-logo.svg" alt="Nubes">
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.169 — Lucee</span></span>
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.170 — Lucee</span></span>
</div>
<div class="content">
@@ -99,7 +99,7 @@
</div>
<button class="btn btn-primary" id="llmBtn" style="width:100%;justify-content:center;margin-top:8px;display:none;background:var(--green);border-color:var(--green);">
<i data-lucide="scale" style="width:16px;height:16px;"></i> Сравнить
<i data-lucide="scale" style="width:16px;height:16px;"></i> Общее сравнение
</button>
<details style="margin-top:8px;font-size:12px;">
@@ -115,7 +115,7 @@
<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>
Классификация и группы <span id="diffStatus" style="font-weight:400;font-size:12px;margin-left:8px;"></span>
</div>
<div class="card-body" id="diffBody"></div>
</div>