From d2894ad7c50e376d1287ae22a4a9e95ca3350fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Wed, 26 Aug 2026 08:40:19 +0300 Subject: [PATCH] =?UTF-8?q?v2.0.13:=20=D1=8D=D1=82=D0=B0=D0=BF=203=20?= =?UTF-8?q?=E2=80=94=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20=D0=BF=D0=B0=D0=BF?= =?UTF-8?q?=D0=BE=D0=BA=20+=20=D1=80=D0=B5=D0=BA=D1=83=D1=80=D1=81=D0=B8?= =?UTF-8?q?=D0=B2=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D1=81=D0=BA=D0=B8=D0=B9=20ZIP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pages_bp: route /upload/ отдаёт ES-модули upload/frontend - index.html: инпут папки (webkitdirectory) + мост listZipFiles - files.js: expandZipClient (рекурсивный ZIP через drhider listZipFiles) - app.js: обработчик выбора папки (фильтр .pdf/.doc/.docx/.zip) --- site/config.py | 2 +- site/routes/pages_bp.py | 15 ++++++++++++++- site/static/app.js | 14 ++++++++++++++ site/static/files.js | 27 ++++++++++++++++++++++++++- site/templates/index.html | 14 +++++++++++--- 5 files changed, 66 insertions(+), 6 deletions(-) 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 @@
+ - + - +