diff --git a/deploy/app.js b/deploy/app.js index d14ddb1..aac1fd3 100644 --- a/deploy/app.js +++ b/deploy/app.js @@ -21,13 +21,15 @@ var batchId = crypto.randomUUID(); // классификация: привяз })(); // Синхронизация БД с таблицей — удалить всё, чего нет в fileQueue -function syncDB() { +async function syncDB() { var keepIds = fileQueue.map(function(f) { return f.doc_id; }).filter(Boolean); - fetch(VM_API + '/api/sync', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({keep_ids: keepIds}) - }).catch(function(){}); + try { + await fetch(VM_API + '/api/sync', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({keep_ids: keepIds}) + }); + } catch(e) { /* сервер недоступен — не критично */ } } // ── Pipeline stepper ───────────────────────────────────────── function stepDone(id) { @@ -58,7 +60,7 @@ function renderTable() { } else { fileTable.innerHTML = fileQueue.map(function(f, i) { return '' + - '' + f.name + '' + + '' + escHtml(f.name) + '' + '' + formatDate(f.lastModified) + '' + '' + formatSize(f.size) + '' + '' + (f.status || '') + '' + @@ -318,18 +320,18 @@ async function loadGroups() { var docsHtml = g.documents.map(function(d) { var reason = ''; if (d.parent_number) { - reason = ' — нет базового договора №' + d.parent_number; + reason = ' — нет базового договора №' + escHtml(d.parent_number); } else if (d.classify_status === 'failed') { - reason = ' — ошибка классификации: ' + (d.error_message || '?'); + reason = ' — ошибка классификации: ' + escHtml(d.error_message || '?'); } else if (!d.doc_type || d.doc_type === 'other') { reason = ' — тип не определён'; } else { reason = ' — нет matching-договора'; } return '
' + - '[' + (d.doc_type || '?') + '] ' + - d.filename + - (d.doc_date ? ' ' + d.doc_date + '' : '') + + '[' + escHtml(d.doc_type || '?') + '] ' + + escHtml(d.filename) + + (d.doc_date ? ' ' + escHtml(d.doc_date) + '' : '') + '' + reason + '' + '
'; }).join(''); @@ -337,14 +339,14 @@ async function loadGroups() { '
' + docsHtml + '
' + '
Загрузите недостающие базовые договоры для этих номеров.
'; } else { - card.innerHTML = '
📄 Договор №' + (g.contract_number || '?') + ' — ' + (g.counterparty || 'контрагент не определён') + '
' + + card.innerHTML = '
📄 Договор №' + escHtml(g.contract_number || '?') + ' — ' + escHtml(g.counterparty || 'контрагент не определён') + '
' + '
' + g.documents.map(function(d) { var typeLabel = {contract: 'договор', supplement: 'допсоглашение', specification: 'спецификация'}[d.doc_type] || d.doc_type; return '
' + - '' + (typeLabel || '?') + ' ' + - d.filename + - (d.doc_date ? ' ' + d.doc_date + '' : '') + + '' + escHtml(typeLabel || '?') + ' ' + + escHtml(d.filename) + + (d.doc_date ? ' ' + escHtml(d.doc_date) + '' : '') + '
'; }).join('') + '
'; card.innerHTML += ''; @@ -723,11 +725,11 @@ async function showText(i) { // ── Результаты классификации ────────────────────────── if (qData.classify_status === 'classified' || qData.classify_raw) { rightHtml += '
🏷️ Классификация LLM
'; - if (qData.doc_type) rightHtml += '
Тип' + qData.doc_type + '
'; - if (qData.own_number) rightHtml += '
Номер' + qData.own_number + '
'; - if (qData.parent_number) rightHtml += '
Родительский' + qData.parent_number + '
'; - if (qData.doc_date) rightHtml += '
Дата' + qData.doc_date + '
'; - if (qData.counterparty) rightHtml += '
Контрагент' + qData.counterparty + '
'; + if (qData.doc_type) rightHtml += '
Тип' + escHtml(qData.doc_type) + '
'; + if (qData.own_number) rightHtml += '
Номер' + escHtml(qData.own_number) + '
'; + if (qData.parent_number) rightHtml += '
Родительский' + escHtml(qData.parent_number) + '
'; + if (qData.doc_date) rightHtml += '
Дата' + escHtml(qData.doc_date) + '
'; + if (qData.counterparty) rightHtml += '
Контрагент' + escHtml(qData.counterparty) + '
'; if (qData.classify_raw) { var rawId = 'raw_' + docId.replace(/-/g,''); rightHtml += '
Сырой ответ LLM'; diff --git a/deploy/app_utils.js b/deploy/app_utils.js index 0a54d46..32879fd 100644 --- a/deploy/app_utils.js +++ b/deploy/app_utils.js @@ -86,6 +86,7 @@ window.closeAbout = closeAbout; * Используется в истории промптов. */ function escHtml(s) { + if (s == null) return ''; var d = document.createElement('div'); d.textContent = s; return d.innerHTML;