v2.0.13: этап 3 — выбор папок + рекурсивный клиентский ZIP
Deploy contracts-flask / validate (push) Canceled after 0s
Deploy contracts-flask / validate (push) Canceled after 0s
- pages_bp: route /upload/<path> отдаёт ES-модули upload/frontend - index.html: инпут папки (webkitdirectory) + мост listZipFiles - files.js: expandZipClient (рекурсивный ZIP через drhider listZipFiles) - app.js: обработчик выбора папки (фильтр .pdf/.doc/.docx/.zip)
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
"""Конфигурация приложения — все настройки в одном месте."""
|
"""Конфигурация приложения — все настройки в одном месте."""
|
||||||
import os
|
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_URL = os.getenv("LLM_API_URL", "https://api.aillm.ru/v1/chat/completions")
|
||||||
LLM_KEY = os.getenv("LLM_API_KEY", "")
|
LLM_KEY = os.getenv("LLM_API_KEY", "")
|
||||||
|
|||||||
+14
-1
@@ -1,8 +1,15 @@
|
|||||||
"""HTML-страницы."""
|
"""HTML-страницы."""
|
||||||
from flask import Blueprint, render_template
|
import os
|
||||||
|
from flask import Blueprint, render_template, send_from_directory
|
||||||
|
|
||||||
pages_bp = Blueprint("pages", __name__)
|
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("/")
|
@pages_bp.route("/")
|
||||||
def index():
|
def index():
|
||||||
@@ -12,3 +19,9 @@ def index():
|
|||||||
@pages_bp.route("/architect")
|
@pages_bp.route("/architect")
|
||||||
def architect():
|
def architect():
|
||||||
return render_template("architect.html")
|
return render_template("architect.html")
|
||||||
|
|
||||||
|
|
||||||
|
@pages_bp.route("/upload/<path:filename>")
|
||||||
|
def upload_frontend(filename):
|
||||||
|
"""Отдать статику ES-модулей upload/frontend (для клиентского ZIP)."""
|
||||||
|
return send_from_directory(_UPLOAD_FRONTEND, filename)
|
||||||
|
|||||||
@@ -116,6 +116,20 @@ fileInput.addEventListener('change', async function() {
|
|||||||
onFilesSelected(newFiles);
|
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() {
|
function showClassifyBtn() {
|
||||||
var btn = document.getElementById('classifyBtn');
|
var btn = document.getElementById('classifyBtn');
|
||||||
|
|||||||
+26
-1
@@ -558,6 +558,31 @@ async function finalizeUpload() {
|
|||||||
lucide.createIcons();
|
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 — все строки сразу, потом загрузка.
|
* ⛔ НЕ МЕНЯТЬ ⛔ onFilesSelected — все строки сразу, потом загрузка.
|
||||||
*
|
*
|
||||||
@@ -574,7 +599,7 @@ async function onFilesSelected(newFiles) {
|
|||||||
for (var i = 0; i < newFiles.length; i++) {
|
for (var i = 0; i < newFiles.length; i++) {
|
||||||
var f = newFiles[i];
|
var f = newFiles[i];
|
||||||
if (f.name.toLowerCase().endsWith('.zip')) {
|
if (f.name.toLowerCase().endsWith('.zip')) {
|
||||||
await addZipFile(f);
|
await expandZipClient(f);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
state.files.push({ name: f.name, lastModified: f.lastModified, size: f.size, file: f, status: { kind: 'connecting', elapsed: 0 }, zip_source: null });
|
state.files.push({ name: f.name, lastModified: f.lastModified, size: f.size, file: f, status: { kind: 'connecting', elapsed: 0 }, zip_source: null });
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<img src="/static/logo.svg" alt="Nubes">
|
<img src="/static/logo.svg" alt="Nubes">
|
||||||
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v2.0.12</span></span>
|
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v2.0.13</span></span>
|
||||||
<div id="pipelineStepper" style="display:flex;gap:8px;font-size:11px;align-items:center;color:var(--muted);">
|
<div id="pipelineStepper" style="display:flex;gap:8px;font-size:11px;align-items:center;color:var(--muted);">
|
||||||
<span id="stepUpload">○ Загрузка</span><span>→</span>
|
<span id="stepUpload">○ Загрузка</span><span>→</span>
|
||||||
<span id="stepClassify">○ Классификация</span><span>→</span>
|
<span id="stepClassify">○ Классификация</span><span>→</span>
|
||||||
@@ -91,6 +91,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<input type="file" id="fileInput" accept=".doc,.docx,.pdf,.zip" multiple style="margin-bottom:6px;width:100%;">
|
<input type="file" id="fileInput" accept=".doc,.docx,.pdf,.zip" multiple style="margin-bottom:6px;width:100%;">
|
||||||
|
<input type="file" id="folderInput" webkitdirectory multiple style="display:none;">
|
||||||
|
<div style="margin-bottom:6px;">
|
||||||
|
<button type="button" class="btn" id="folderBtn" style="font-size:12px;height:30px;">📁 Выбрать папку</button>
|
||||||
|
</div>
|
||||||
<div style="text-align:right;font-size:11px;color:var(--muted);margin-bottom:6px;">Порядок определяется автоматически при классификации</div>
|
<div style="text-align:right;font-size:11px;color:var(--muted);margin-bottom:6px;">Порядок определяется автоматически при классификации</div>
|
||||||
<div style="font-size:11px;color:var(--muted);margin-bottom:6px;">⚠ При совпадении имён — запрос на перезапись (OK / Отмена). Файлы из ZIP-архивов загружаются через тот же поток.</div>
|
<div style="font-size:11px;color:var(--muted);margin-bottom:6px;">⚠ При совпадении имён — запрос на перезапись (OK / Отмена). Файлы из ZIP-архивов загружаются через тот же поток.</div>
|
||||||
|
|
||||||
@@ -216,11 +220,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import { listZipFiles } from '/upload/zip/list_zip_files.js';
|
||||||
|
window.listZipFiles = listZipFiles;
|
||||||
|
</script>
|
||||||
<script src="/static/state.js?v=2.0.8"></script>
|
<script src="/static/state.js?v=2.0.8"></script>
|
||||||
<script src="/static/app_utils.js?v=2.0.8"></script>
|
<script src="/static/app_utils.js?v=2.0.8"></script>
|
||||||
<script src="/static/files.js?v=2.0.11"></script>
|
<script src="/static/files.js?v=2.0.13"></script>
|
||||||
<script src="/static/groups.js?v=2.0.8"></script>
|
<script src="/static/groups.js?v=2.0.8"></script>
|
||||||
<script src="/static/compare.js?v=2.0.8"></script>
|
<script src="/static/compare.js?v=2.0.8"></script>
|
||||||
<script src="/static/app.js?v=2.0.10"></script>
|
<script src="/static/app.js?v=2.0.13"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user