v1.0.118: ZIP upload — unzip on VM, add files to queue individually, flat structure only

This commit is contained in:
2026-06-23 08:01:04 +04:00
parent 0944a4c9d1
commit ebe35dce42
3 changed files with 136 additions and 5 deletions
+74 -5
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.117 — Lucee</span></span>
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.118 — Lucee</span></span>
</div>
<div class="content">
@@ -85,7 +85,8 @@
Загрузка договоров/приложений/спецификаций
</div>
<div class="card-body">
<input type="file" id="fileInput" accept=".docx,.doc,.pdf,.zip" multiple style="margin-bottom:12px;width:100%;">
<input type="file" id="fileInput" accept=".docx,.doc,.pdf,.zip" multiple style="margin-bottom:6px;width:100%;">
<div style="text-align:right;font-size:11px;color:var(--muted);margin-bottom:6px;">↕ первый в списке — основной договор, порядок меняется стрелками</div>
<div class="table-wrap">
<table>
@@ -121,7 +122,7 @@
<div class="card" id="chatCard" style="display:none;margin-top:16px;">
<div class="card-header">
<i data-lucide="message-circle" style="width:18px;height:18px;"></i>
Задать вопрос по договору
Задать вопрос по договору <span style="font-weight:400;font-size:11px;color:var(--muted);">— используются все загруженные документы</span>
</div>
<div class="card-body">
<div id="chatMessages" style="margin-bottom:8px;font-size:13px;"></div>
@@ -197,7 +198,7 @@ function formatDate(ts) {
function renderTable() {
if (fileQueue.length === 0) {
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 {
fileTable.innerHTML = fileQueue.map(function(f, i) {
var isFirst = (i === 0);
@@ -247,8 +248,76 @@ fileInput.addEventListener('change', async function() {
for (var i = 0; i < newFiles.length; i++) {
var f = newFiles[i];
var isZip = f.name.toLowerCase().endsWith('.zip');
if (isZip) {
// ZIP: показать временную строку, распаковать, убрать
var zipEntry = { name: f.name, lastModified: f.lastModified, size: f.size, status: '⏳ распаковка...' };
fileQueue.push(zipEntry);
var zipIdx = fileQueue.length - 1;
renderTable();
try {
var zipResp = await fetch('/unzip-upload', {
method: 'POST',
body: f,
headers: {'Content-Type': 'application/zip'}
});
var zipData = await zipResp.json();
if (!zipData.ok || !zipData.files) throw new Error('unzip failed');
// Убрать строку ZIP из таблицы
fileQueue.splice(zipIdx, 1);
renderTable();
// Добавить файлы из архива, пропуская дубликаты
for (var zi = 0; zi < zipData.files.length; zi++) {
var zf = zipData.files[zi];
if (zf.error) continue; // пропускаем ошибки
// Проверить дубликат по имени
var dup = -1;
for (var dj = 0; dj < fileQueue.length; dj++) {
if (fileQueue[dj].name === zf.filename) { dup = dj; break; }
}
if (dup >= 0) continue; // уже есть — не добавляем
var zEntry = {
name: zf.filename, size: zf.size,
doc_id: zf.doc_id, uploaded: true,
status: '⏳ парсинг...'
};
fileQueue.push(zEntry);
var zIdx = fileQueue.length - 1;
if (!contractId && zf.contract_id) contractId = zf.contract_id;
renderTable();
// Авто-парсинг
var zt0 = Date.now();
try {
var zpr = await fetch('/parser.cfm?doc_id=' + zf.doc_id);
var zpd = await zpr.json();
var zelapsed = ((Date.now() - zt0) / 1000).toFixed(1);
if (zpd.OK && zpd.STATUS === 'parsed') {
fileQueue[zIdx].parsed = true;
fileQueue[zIdx].status = '<span class="status-ok">✓ ' + zpd.ELEMENT_COUNT + ' эл. (' + zelapsed + 'с)</span>';
} else {
fileQueue[zIdx].status = '<span class="status-err">✗ ' + (zpd.ERROR_COUNT > 0 ? (zpd.ERRORS && zpd.ERRORS[0] || 'ошибка') : 'ошибка') + '</span>';
}
} catch(zpe) {
fileQueue[zIdx].status = '<span class="status-err">✗ парсинг: сеть</span>';
}
renderTable();
}
} catch(ze) {
fileQueue[zipIdx].status = '<span class="status-err">✗ ZIP: ' + ze.message + '</span>';
renderTable();
}
continue; // ZIP обработан, переходим к следующему файлу
}
// Обычный файл (не ZIP)
var entry = { name: f.name, lastModified: f.lastModified, size: f.size, file: f, status: '↑ 0%' };
// Заменить старый файл с тем же именем, если есть
var existingIdx = -1;
for (var j = 0; j < fileQueue.length; j++) {
if (fileQueue[j].name === f.name) { existingIdx = j; break; }