"""contracts-flask — Flask-фронтенд для сверки договоров. 1:1 функционал с Lucee-версией. Все тяжёлые операции — на ВМ (contracts.kube5s.ru). """ import os import httpx from flask import Flask, render_template, request, jsonify app = Flask(__name__) 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", "") LLM_MODEL = os.environ.get("LLM_MODEL", "gpt-oss-120b") @app.route("/") def index(): return render_template("index.html", vm_api=VM_API) @app.route("/chat", methods=["POST"]) def chat(): contract_id = request.args.get("contract_id", "") question = request.form.get("question", "") if not contract_id or not question: return jsonify({"ok": False, "error": "contract_id and question required"}) try: with httpx.Client(timeout=10) as client: resp = client.get(f"{VM_API}/api/spec-current", params={"contract_id": contract_id}) resp.raise_for_status() rows = resp.json().get("rows", []) except Exception as e: return jsonify({"ok": False, "error": f"VM error: {e}"}) if not rows: return jsonify({"ok": True, "answer": "Нет данных. Сначала запустите сравнение."}) ctx = "\n".join(f"{r.get('name','')} | цена={r.get('price')} | объём={r.get('qty')} | сумма={r.get('sum')} | начало={r.get('date_start')}" for r in rows) prompt = f"Ты — анализатор договоров. Данные:\n{ctx}\n\nВопрос: {question}\nОтветь кратко, только по данным." try: with httpx.Client(http2=True, timeout=120) as client: resp = client.post(LLM_URL, json={ "model": LLM_MODEL, "messages": [{"role": "user", "content": prompt}], "max_tokens": 1000, "temperature": 0.1, }, headers={"Authorization": f"Bearer {LLM_KEY}", "Content-Type": "application/json"}) resp.raise_for_status() return jsonify({"ok": True, "answer": resp.json()["choices"][0]["message"]["content"]}) except Exception as e: return jsonify({"ok": False, "error": f"LLM error: {e}"}) @app.route("/api/prompts", methods=["GET"]) def prompts_get(): try: with httpx.Client(timeout=10) as client: resp = client.get(f"{VM_API}/api/prompts", params=request.args) return jsonify(resp.json()) except Exception as e: return jsonify({"ok": False, "error": str(e)}) @app.route("/api/prompts/list", methods=["GET"]) def prompts_list(): try: with httpx.Client(timeout=10) as client: resp = client.get(f"{VM_API}/api/prompts/list") return jsonify(resp.json()) except Exception as e: return jsonify({"ok": False, "error": str(e)}) @app.route("/api/prompts/save", methods=["POST"]) def prompts_save(): try: with httpx.Client(timeout=10) as client: resp = client.post(f"{VM_API}/api/prompts/save", json=request.get_json()) return jsonify(resp.json()) except Exception as e: return jsonify({"ok": False, "error": str(e)}) @app.route("/api/prompts/activate", methods=["POST"]) def prompts_activate(): try: with httpx.Client(timeout=10) as client: resp = client.post(f"{VM_API}/api/prompts/activate", json=request.get_json()) return jsonify(resp.json()) except Exception as e: return jsonify({"ok": False, "error": str(e)}) @app.route("/architect") def architect(): return render_template("architect.html") @app.route("/ci-cd") def ci_cd(): return render_template("ci-cd.html") @app.route("/DrHider") def drhider(): return render_template("drhider.html") @app.route("/api/drhider", methods=["POST"]) def api_drhider(): """Обфускация: файлы → VM (LLM-NER) → ZIP.""" import httpx from flask import send_file uploaded = request.files.getlist("files") if not uploaded: return jsonify({"ok": False, "error": "Нет файлов"}), 400 # Отправляем файлы на VM try: files_data = [] for f in uploaded: if f.filename: files_data.append(("files", (f.filename, f.read(), f.content_type or ""))) with httpx.Client(timeout=300) as client: resp = client.post(f"{VM_API}/api/drhider", files=files_data) resp.raise_for_status() except httpx.HTTPStatusError as e: try: detail = e.response.json().get("error", str(e)) except Exception: detail = str(e) return jsonify({"ok": False, "error": detail}), 500 except Exception as e: return jsonify({"ok": False, "error": str(e)}), 500 import io buf = io.BytesIO(resp.content) buf.seek(0) return send_file(buf, mimetype="application/zip", as_attachment=True, download_name="drhider_output.zip") @app.route("/health") def health(): return {"ok": True, "service": "contracts-flask"} if __name__ == "__main__": app.run(host="0.0.0.0", port=5000) # ── Совместимость с Lucee-путями ────────────────────────────── @app.route("/chat.cfm", methods=["POST"]) def chat_cfm(): return chat() @app.route("/prompt.cfm", methods=["GET"]) def prompt_cfm(): return prompts_get()