v1.0.109: ↕ arrows replace Базовый radio, order param in SSE, setInitial removed

This commit is contained in:
2026-06-23 00:43:33 +04:00
parent acec6729b6
commit ded5071dae
2 changed files with 33 additions and 15 deletions
+6
View File
@@ -119,6 +119,12 @@ class Handler(BaseHTTPRequestHandler):
f"JOIN documents d ON s.document_id=d.id " f"JOIN documents d ON s.document_id=d.id "
f"WHERE s.contract_id='{cid}' AND d.elements_json IS NOT NULL ORDER BY s.created_at" f"WHERE s.contract_id='{cid}' AND d.elements_json IS NOT NULL ORDER BY s.created_at"
) )
# Reorder if client specified &order=...
order_ids = (params.get("order", [None])[0] or "").strip()
if order_ids:
order_list = [x.strip() for x in order_ids.split(",") if x.strip()]
order_map = {oid: i for i, oid in enumerate(order_list)}
supps.sort(key=lambda s: order_map.get(s["id"], 999999))
if not supps: if not supps:
self._sse({"type": "error", "message": "Нет распарсенных файлов"}) self._sse({"type": "error", "message": "Нет распарсенных файлов"})
return return
+27 -15
View File
@@ -39,6 +39,10 @@
.info-btn { cursor: pointer; color: var(--muted); background: none; border: none; padding: 2px 4px; font-size: 14px; display: none; } .info-btn { cursor: pointer; color: var(--muted); background: none; border: none; padding: 2px 4px; font-size: 14px; display: none; }
.info-btn:hover { color: var(--brand-primary); } .info-btn:hover { color: var(--brand-primary); }
.info-btn.visible { display: inline; } .info-btn.visible { display: inline; }
.first-row td { background: rgba(37,99,235,.06); font-weight: 600; }
.arrow-btn { cursor: pointer; color: var(--muted); background: none; border: none; padding: 2px; font-size: 12px; line-height: 1; }
.arrow-btn:hover { color: var(--brand-primary); }
.arrow-btn:disabled { opacity: .3; cursor: default; color: var(--muted); }
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,.4); z-index: 100; justify-content: center; align-items: center; } .modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,.4); z-index: 100; justify-content: center; align-items: center; }
.modal-overlay.open { display: flex; } .modal-overlay.open { display: flex; }
.modal { background: var(--background); border-radius: 12px; border: 1px solid var(--brand-gray); box-shadow: 0 4px 24px rgba(0,0,0,.15); max-width: 520px; width: 90%; max-height: 80vh; display: flex; flex-direction: column; } .modal { background: var(--background); border-radius: 12px; border: 1px solid var(--brand-gray); box-shadow: 0 4px 24px rgba(0,0,0,.15); max-width: 520px; width: 90%; max-height: 80vh; display: flex; flex-direction: column; }
@@ -71,7 +75,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 AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.108 — Lucee</span></span> <span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.109 — Lucee</span></span>
</div> </div>
<div class="content"> <div class="content">
@@ -86,7 +90,7 @@
<div class="table-wrap"> <div class="table-wrap">
<table> <table>
<thead> <thead>
<tr><th>Имя</th><th style="width:130px;">Изменён</th><th style="width:90px;">Размер</th><th style="width:115px;">Парсинг</th><th style="width:30px;">Базовый</th><th style="width:30px;">Текст</th><th style="width:30px;">✕</th></tr> <tr><th>Имя</th><th style="width:130px;">Изменён</th><th style="width:90px;">Размер</th><th style="width:115px;">Парсинг</th><th style="width:30px;"></th><th style="width:30px;">Текст</th><th style="width:30px;">✕</th></tr>
</thead> </thead>
<tbody id="fileTable"></tbody> <tbody id="fileTable"></tbody>
</table> </table>
@@ -172,16 +176,16 @@ function renderTable() {
fileTable.innerHTML = '<tr class="empty-row"><td colspan="7">Нет файлов — выберите .docx / .pdf / .zip</td></tr>'; fileTable.innerHTML = '<tr class="empty-row"><td colspan="7">Нет файлов — выберите .docx / .pdf / .zip</td></tr>';
} else { } else {
fileTable.innerHTML = fileQueue.map(function(f, i) { fileTable.innerHTML = fileQueue.map(function(f, i) {
var radio = ''; var isFirst = (i === 0);
if (f.supp_id) { var isLast = (i === fileQueue.length - 1);
radio = '<input type="radio" name="base_doc" value="' + f.supp_id + '"' + (f.supp_type === 'initial' ? ' checked' : '') + ' onchange="setInitial(\'' + f.supp_id + '\')" title="Выбрать как базовый договор">'; var arrows = '<button class="arrow-btn" onclick="moveUp(' + i + ')" ' + (isFirst ? 'disabled' : '') + ' title="Вверх">▲</button>' +
} '<button class="arrow-btn" onclick="moveDown(' + i + ')" ' + (isLast ? 'disabled' : '') + ' title="Вниз">▼</button>';
return '<tr id="row_' + i + '">' + return '<tr id="row_' + i + '" class="' + (isFirst ? 'first-row' : '') + '">' +
'<td class="name-cell">' + f.name + '</td>' + '<td class="name-cell">' + f.name + '</td>' +
'<td style="font-size:12px;color:var(--muted);">' + formatDate(f.lastModified) + '</td>' + '<td style="font-size:12px;color:var(--muted);">' + formatDate(f.lastModified) + '</td>' +
'<td class="num-cell" style="font-size:12px;color:var(--muted);">' + formatSize(f.size) + '</td>' + '<td class="num-cell" style="font-size:12px;color:var(--muted);">' + formatSize(f.size) + '</td>' +
'<td class="status-cell">' + (f.status || '') + '</td>' + '<td class="status-cell">' + (f.status || '') + '</td>' +
'<td style="text-align:center;">' + radio + '</td>' + '<td style="text-align:center;">' + arrows + '</td>' +
'<td><button class="info-btn' + (f.doc_id ? ' visible' : '') + '" onclick="showText(' + i + ')" title="Текст / Распарсено"><i data-lucide="file-text" style="width:16px;height:16px;"></i></button></td>' + '<td><button class="info-btn' + (f.doc_id ? ' visible' : '') + '" onclick="showText(' + i + ')" title="Текст / Распарсено"><i data-lucide="file-text" style="width:16px;height:16px;"></i></button></td>' +
'<td><button class="remove-btn" onclick="removeFile(' + i + ')" title="Удалить"><i data-lucide="trash-2" style="width:16px;height:16px;"></i></button></td>' + '<td><button class="remove-btn" onclick="removeFile(' + i + ')" title="Удалить"><i data-lucide="trash-2" style="width:16px;height:16px;"></i></button></td>' +
'</tr>'; '</tr>';
@@ -197,6 +201,19 @@ function removeFile(i) {
} }
window.removeFile = removeFile; window.removeFile = removeFile;
function moveUp(i) {
if (i <= 0) return;
var tmp = fileQueue[i]; fileQueue[i] = fileQueue[i-1]; fileQueue[i-1] = tmp;
renderTable();
}
function moveDown(i) {
if (i >= fileQueue.length - 1) return;
var tmp = fileQueue[i]; fileQueue[i] = fileQueue[i+1]; fileQueue[i+1] = tmp;
renderTable();
}
window.moveUp = moveUp;
window.moveDown = moveDown;
// ── Автозагрузка при выборе файлов ────────────────────────── // ── Автозагрузка при выборе файлов ──────────────────────────
fileInput.addEventListener('change', async function() { fileInput.addEventListener('change', async function() {
var newFiles = Array.from(fileInput.files); var newFiles = Array.from(fileInput.files);
@@ -337,7 +354,8 @@ document.getElementById('llmBtn').addEventListener('click', function() {
diffStatus.textContent = '⏳ ' + ((Date.now() - llmStart) / 1000).toFixed(1) + 'с'; diffStatus.textContent = '⏳ ' + ((Date.now() - llmStart) / 1000).toFixed(1) + 'с';
}, 200); }, 200);
var es = new EventSource('https://contracts.kube5s.ru/process-v2?contract_id=' + contractId); var order = fileQueue.map(function(f) { return f.supp_id; }).filter(Boolean).join(',');
var es = new EventSource('https://contracts.kube5s.ru/process-v2?contract_id=' + contractId + (order ? '&order=' + encodeURIComponent(order) : ''));
var sections = {}; // supplement_id → DOM element var sections = {}; // supplement_id → DOM element
es.onmessage = function(e) { es.onmessage = function(e) {
@@ -444,12 +462,6 @@ async function refreshSupps() {
} catch(e) { console.log('refreshSupps error:', e); } } catch(e) { console.log('refreshSupps error:', e); }
} }
function setInitial(suppId) {
fetch('/api.cfm?action=set_initial&supplement_id=' + suppId)
.then(function() { refreshSupps(); });
}
window.setInitial = setInitial;
// ── Чат ────────────────────────────────────────────────────── // ── Чат ──────────────────────────────────────────────────────
document.getElementById('chatSend').addEventListener('click', function() { document.getElementById('chatSend').addEventListener('click', function() {
var input = document.getElementById('chatInput'); var input = document.getElementById('chatInput');