feat: async DrHider — POST job + poll + download, v0.5/v1.0.188
Deploy contracts-flask / validate (push) Successful in 0s
Deploy contracts-flask / validate (push) Successful in 0s
This commit is contained in:
@@ -6,7 +6,7 @@ DrHider — обфускация документов (двухпроходна
|
|||||||
|
|
||||||
Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение.
|
Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение.
|
||||||
"""
|
"""
|
||||||
DRHIDER_VERSION = "0.3"
|
DRHIDER_VERSION = "0.5"
|
||||||
import io
|
import io
|
||||||
import csv
|
import csv
|
||||||
import re
|
import re
|
||||||
|
|||||||
+51
-10
@@ -3,11 +3,17 @@
|
|||||||
Все тяжёлые операции — на ВМ (contracts.kube5s.ru).
|
Все тяжёлые операции — на ВМ (contracts.kube5s.ru).
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import uuid
|
||||||
|
import threading
|
||||||
import httpx
|
import httpx
|
||||||
from flask import Flask, render_template, request, jsonify
|
from flask import Flask, render_template, request, jsonify
|
||||||
|
|
||||||
app = Flask(__name__)
|
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")
|
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_URL = os.environ.get("LLM_API_URL", "https://api.aillm.ru/v1/chat/completions")
|
||||||
LLM_KEY = os.environ.get("LLM_API_KEY", "")
|
LLM_KEY = os.environ.get("LLM_API_KEY", "")
|
||||||
@@ -110,9 +116,7 @@ def drhider():
|
|||||||
|
|
||||||
@app.route("/api/drhider", methods=["POST"])
|
@app.route("/api/drhider", methods=["POST"])
|
||||||
def api_drhider():
|
def api_drhider():
|
||||||
"""Обфускация: файлы → LLM-NER → ZIP."""
|
"""Обфускация: старт фоновой задачи."""
|
||||||
import io
|
|
||||||
from flask import send_file
|
|
||||||
from services.drhider import obfuscate_files
|
from services.drhider import obfuscate_files
|
||||||
|
|
||||||
uploaded = request.files.getlist("files")
|
uploaded = request.files.getlist("files")
|
||||||
@@ -123,13 +127,50 @@ def api_drhider():
|
|||||||
if not files:
|
if not files:
|
||||||
return jsonify({"ok": False, "error": "Нет файлов"}), 400
|
return jsonify({"ok": False, "error": "Нет файлов"}), 400
|
||||||
|
|
||||||
try:
|
job_id = uuid.uuid4().hex
|
||||||
zip_data, _csv = obfuscate_files(files)
|
with _drhider_lock:
|
||||||
buf = io.BytesIO(zip_data)
|
_drhider_jobs[job_id] = {"done": False, "data": None, "error": None}
|
||||||
buf.seek(0)
|
|
||||||
return send_file(buf, mimetype="application/zip", as_attachment=True, download_name="drhider_output.zip")
|
def _process():
|
||||||
except Exception as e:
|
try:
|
||||||
return jsonify({"ok": False, "error": str(e)}), 500
|
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/<job_id>/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/<job_id>/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")
|
@app.route("/health")
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ DrHider — обфускация документов (двухпроходна
|
|||||||
|
|
||||||
Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение.
|
Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение.
|
||||||
"""
|
"""
|
||||||
DRHIDER_VERSION = "0.3"
|
DRHIDER_VERSION = "0.5"
|
||||||
import io
|
import io
|
||||||
import csv
|
import csv
|
||||||
import re
|
import re
|
||||||
|
|||||||
+26
-12
@@ -64,7 +64,7 @@
|
|||||||
<a href="/">Сверка договоров</a>
|
<a href="/">Сверка договоров</a>
|
||||||
<span class="sep">|</span>
|
<span class="sep">|</span>
|
||||||
<strong>DrHider</strong>
|
<strong>DrHider</strong>
|
||||||
<span style="font-size:11px;color:var(--muted);">v0.4</span>
|
<span style="font-size:11px;color:var(--muted);">v0.5</span>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
@@ -121,21 +121,35 @@ function render() {
|
|||||||
BTN.onclick = async () => {
|
BTN.onclick = async () => {
|
||||||
if (!files.length) return;
|
if (!files.length) return;
|
||||||
BTN.disabled = true;
|
BTN.disabled = true;
|
||||||
setStatus('progress', '⏳ Отправка на сервер...');
|
setStatus('progress', '⏳ Отправка...');
|
||||||
const fd = new FormData(); files.forEach(f => fd.append('files', f));
|
const fd = new FormData(); files.forEach(f => fd.append('files', f));
|
||||||
try {
|
try {
|
||||||
setStatus('progress', '⏳ Обработка (может занять до минуты)...');
|
// Шаг 1: старт задачи
|
||||||
const r = await fetch('/api/drhider', { method:'POST', body:fd });
|
const r1 = await fetch('/api/drhider', { method:'POST', body:fd });
|
||||||
if (!r.ok) {
|
if (!r1.ok) throw new Error(await r1.text());
|
||||||
const txt = await r.text();
|
const j = await r1.json();
|
||||||
throw new Error(txt);
|
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) {
|
} catch(e) {
|
||||||
setStatus('error', '❌ ' + (e.name === 'TimeoutError' ? 'Сервер не ответил за 5 минут' : e.message || 'Неизвестная ошибка'));
|
setStatus('error', '❌ ' + (e.message || 'Ошибка'));
|
||||||
console.error('DrHider error:', e);
|
|
||||||
}
|
}
|
||||||
BTN.disabled = false;
|
BTN.disabled = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;">v1.0.187-flask</span></span>
|
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.188-flask</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user