diff --git a/deploy/services/drhider.py b/deploy/services/drhider.py index 7101322..4a10a30 100644 --- a/deploy/services/drhider.py +++ b/deploy/services/drhider.py @@ -6,7 +6,7 @@ DrHider — обфускация документов (двухпроходна Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение. """ -DRHIDER_VERSION = "0.3" +DRHIDER_VERSION = "0.5" import io import csv import re diff --git a/site/app.py b/site/app.py index ab35a07..1dd49ee 100644 --- a/site/app.py +++ b/site/app.py @@ -3,11 +3,17 @@ Все тяжёлые операции — на ВМ (contracts.kube5s.ru). """ import os +import uuid +import threading import httpx from flask import Flask, render_template, request, jsonify app = Flask(__name__) +# DrHider job store (in-memory, no persistence) +_drhider_jobs = {} +_drhider_lock = threading.Lock() + VM_API = os.environ.get("VM_API", "https://contracts.kube5s.ru") LLM_URL = os.environ.get("LLM_API_URL", "https://api.aillm.ru/v1/chat/completions") LLM_KEY = os.environ.get("LLM_API_KEY", "") @@ -110,9 +116,7 @@ def drhider(): @app.route("/api/drhider", methods=["POST"]) def api_drhider(): - """Обфускация: файлы → LLM-NER → ZIP.""" - import io - from flask import send_file + """Обфускация: старт фоновой задачи.""" from services.drhider import obfuscate_files uploaded = request.files.getlist("files") @@ -123,13 +127,50 @@ def api_drhider(): if not files: return jsonify({"ok": False, "error": "Нет файлов"}), 400 - try: - zip_data, _csv = obfuscate_files(files) - buf = io.BytesIO(zip_data) - buf.seek(0) - return send_file(buf, mimetype="application/zip", as_attachment=True, download_name="drhider_output.zip") - except Exception as e: - return jsonify({"ok": False, "error": str(e)}), 500 + job_id = uuid.uuid4().hex + with _drhider_lock: + _drhider_jobs[job_id] = {"done": False, "data": None, "error": None} + + def _process(): + try: + zip_data, _csv = obfuscate_files(files) + with _drhider_lock: + _drhider_jobs[job_id] = {"done": True, "data": zip_data, "error": None} + except Exception as e: + with _drhider_lock: + _drhider_jobs[job_id] = {"done": True, "data": None, "error": str(e)} + + threading.Thread(target=_process, daemon=True).start() + return jsonify({"ok": True, "job_id": job_id}) + + +@app.route("/api/drhider//status") +def api_drhider_status(job_id): + """Статус задачи.""" + with _drhider_lock: + job = _drhider_jobs.get(job_id) + if not job: + return jsonify({"ok": False, "error": "Неизвестный job_id"}), 404 + return jsonify({"ok": True, "done": job["done"], "error": job["error"]}) + + +@app.route("/api/drhider//download") +def api_drhider_download(job_id): + """Скачать результат.""" + from flask import send_file + import io + with _drhider_lock: + job = _drhider_jobs.get(job_id) + if not job or not job["done"]: + return jsonify({"ok": False, "error": "Не готов"}), 404 + if job["error"]: + return jsonify({"ok": False, "error": job["error"]}), 500 + buf = io.BytesIO(job["data"]) + buf.seek(0) + # Очистка + with _drhider_lock: + _drhider_jobs.pop(job_id, None) + return send_file(buf, mimetype="application/zip", as_attachment=True, download_name="drhider_output.zip") @app.route("/health") diff --git a/site/services/drhider.py b/site/services/drhider.py index 7101322..4a10a30 100644 --- a/site/services/drhider.py +++ b/site/services/drhider.py @@ -6,7 +6,7 @@ DrHider — обфускация документов (двухпроходна Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение. """ -DRHIDER_VERSION = "0.3" +DRHIDER_VERSION = "0.5" import io import csv import re diff --git a/site/templates/drhider.html b/site/templates/drhider.html index 6989bbc..03caa5a 100644 --- a/site/templates/drhider.html +++ b/site/templates/drhider.html @@ -64,7 +64,7 @@ Сверка договоров | DrHider - v0.4 + v0.5
@@ -121,21 +121,35 @@ function render() { BTN.onclick = async () => { if (!files.length) return; BTN.disabled = true; - setStatus('progress', '⏳ Отправка на сервер...'); + setStatus('progress', '⏳ Отправка...'); const fd = new FormData(); files.forEach(f => fd.append('files', f)); try { - setStatus('progress', '⏳ Обработка (может занять до минуты)...'); - const r = await fetch('/api/drhider', { method:'POST', body:fd }); - if (!r.ok) { - const txt = await r.text(); - throw new Error(txt); + // Шаг 1: старт задачи + const r1 = await fetch('/api/drhider', { method:'POST', body:fd }); + if (!r1.ok) throw new Error(await r1.text()); + const j = await r1.json(); + if (!j.ok) throw new Error(j.error); + const jobId = j.job_id; + + // Шаг 2: опрос + for (let i = 0; i < 120; i++) { + await new Promise(r => setTimeout(r, 2000)); + const r2 = await fetch('/api/drhider/' + jobId + '/status'); + const s = await r2.json(); + if (s.error) throw new Error(s.error); + if (s.done) { + setStatus('progress', '⏳ Скачивание...'); + const r3 = await fetch('/api/drhider/' + jobId + '/download'); + if (!r3.ok) throw new Error(await r3.text()); + const blob = await r3.blob(); + const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'drhider_output.zip'; a.click(); + setStatus('done', '✅ Готово!'); + break; + } + setStatus('progress', '⏳ Обработка... (' + ((i+1)*2) + 'с)'); } - const blob = await r.blob(); - const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'drhider_output.zip'; a.click(); - setStatus('done', '✅ Готово! ZIP скачан.'); } catch(e) { - setStatus('error', '❌ ' + (e.name === 'TimeoutError' ? 'Сервер не ответил за 5 минут' : e.message || 'Неизвестная ошибка')); - console.error('DrHider error:', e); + setStatus('error', '❌ ' + (e.message || 'Ошибка')); } BTN.disabled = false; }; diff --git a/site/templates/index.html b/site/templates/index.html index dd50c0d..656c40a 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -73,7 +73,7 @@
Nubes - Сверка договоров — LLM AI-driven Event Sourcing v1.0.187-flask + Сверка договоров — LLM AI-driven Event Sourcing v1.0.188-flask
○ Загрузка ○ Классификация