diff --git a/site/config.py b/site/config.py index 78ac260..818a981 100644 --- a/site/config.py +++ b/site/config.py @@ -1,7 +1,7 @@ """Конфигурация приложения — все настройки в одном месте.""" import os -VERSION = "2.0.12" +VERSION = "2.0.13" LLM_URL = os.getenv("LLM_API_URL", "https://api.aillm.ru/v1/chat/completions") LLM_KEY = os.getenv("LLM_API_KEY", "") diff --git a/site/routes/pages_bp.py b/site/routes/pages_bp.py index 09b17a5..07f91d0 100644 --- a/site/routes/pages_bp.py +++ b/site/routes/pages_bp.py @@ -1,8 +1,15 @@ """HTML-страницы.""" -from flask import Blueprint, render_template +import os +from flask import Blueprint, render_template, send_from_directory pages_bp = Blueprint("pages", __name__) +# Переиспользуемый модуль upload: отдаём ES-модули фронтенда браузеру (как drhider) +_UPLOAD_FRONTEND = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), + "upload", "frontend", +) + @pages_bp.route("/") def index(): @@ -12,3 +19,9 @@ def index(): @pages_bp.route("/architect") def architect(): return render_template("architect.html") + + +@pages_bp.route("/upload/") +def upload_frontend(filename): + """Отдать статику ES-модулей upload/frontend (для клиентского ZIP).""" + return send_from_directory(_UPLOAD_FRONTEND, filename) diff --git a/site/static/app.js b/site/static/app.js index 0f09bf8..0f78b11 100644 --- a/site/static/app.js +++ b/site/static/app.js @@ -116,6 +116,20 @@ fileInput.addEventListener('change', async function() { onFilesSelected(newFiles); }); +// Выбор папки (webkitdirectory) — та же обработка, что и файлы +var folderInput = document.getElementById('folderInput'); +var folderBtn = document.getElementById('folderBtn'); +if (folderBtn && folderInput) { + folderBtn.addEventListener('click', function() { folderInput.click(); }); + folderInput.addEventListener('change', function() { + var files = Array.from(folderInput.files).filter(function(f) { + var n = f.name.toLowerCase(); + return n.endsWith('.pdf') || n.endsWith('.doc') || n.endsWith('.docx') || n.endsWith('.zip'); + }); + if (files.length) onFilesSelected(files); + }); +} + // ── Классификация ────────────────────────────────────────── function showClassifyBtn() { var btn = document.getElementById('classifyBtn'); diff --git a/site/static/files.js b/site/static/files.js index a900588..76a8bf5 100644 --- a/site/static/files.js +++ b/site/static/files.js @@ -558,6 +558,31 @@ async function finalizeUpload() { lucide.createIcons(); } +/** + * expandZipClient(f) — клиентское рекурсивное раскрытие ZIP (drhider listZipFiles). + * Каждый документ из архива (включая вложенные zip) добавляется в state.files + * с zip_source = имя архива. Fallback: если раскрыть не удалось — архив как есть. + */ +async function expandZipClient(f) { + var nested = []; + try { + if (window.listZipFiles) { + nested = await window.listZipFiles(f, ['.pdf', '.doc', '.docx']); + } + } catch (e) { + nested = []; + } + if (!nested || nested.length === 0) { + state.files.push({ name: f.name, lastModified: f.lastModified, size: f.size, file: f, status: { kind: 'connecting', elapsed: 0 }, zip_source: null }); + state.files[state.files.length - 1]._pendingFile = f; + return; + } + for (var z = 0; z < nested.length; z++) { + state.files.push({ name: nested[z].name, lastModified: nested[z].lastModified, size: nested[z].size, file: nested[z], status: { kind: 'connecting', elapsed: 0 }, zip_source: f.name }); + state.files[state.files.length - 1]._pendingFile = nested[z]; + } +} + /** * ⛔ НЕ МЕНЯТЬ ⛔ onFilesSelected — все строки сразу, потом загрузка. * @@ -574,7 +599,7 @@ async function onFilesSelected(newFiles) { for (var i = 0; i < newFiles.length; i++) { var f = newFiles[i]; if (f.name.toLowerCase().endsWith('.zip')) { - await addZipFile(f); + await expandZipClient(f); continue; } state.files.push({ name: f.name, lastModified: f.lastModified, size: f.size, file: f, status: { kind: 'connecting', elapsed: 0 }, zip_source: null }); diff --git a/site/templates/index.html b/site/templates/index.html index 74f03b4..e97dd66 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -73,7 +73,7 @@
Nubes - Сверка договоров — LLM AI-driven Event Sourcing v2.0.12 + Сверка договоров — LLM AI-driven Event Sourcing v2.0.13
○ Загрузка ○ Классификация @@ -91,6 +91,10 @@
+ +
+ +
Порядок определяется автоматически при классификации
⚠ При совпадении имён — запрос на перезапись (OK / Отмена). Файлы из ZIP-архивов загружаются через тот же поток.
@@ -216,11 +220,15 @@
+ - + - +