from __future__ import annotations import asyncio from collections import defaultdict from flask import Flask, jsonify, request from .auto_draw import auto_draw_preview from .logger import setup_logger from .orchestrator import Orchestrator from .rules import extract_image_urls from .runtime_switch import is_listen_only, set_listen_only from .task_manager import TaskManager def create_http_app(task_manager: TaskManager | None = None) -> Flask: app = Flask(__name__) logger = setup_logger() tm = task_manager or TaskManager() sim_orch = Orchestrator() sim_pending_images: dict[str, list[str]] = defaultdict(list) sim_recent_dialogue: dict[str, list[dict]] = defaultdict(list) def _sim_append_dialogue(key: str, role: str, text: str) -> None: t = str(text or "").strip() if not t: return sim_recent_dialogue[key].append({"role": role, "text": t}) if len(sim_recent_dialogue[key]) > 24: sim_recent_dialogue[key] = sim_recent_dialogue[key][-24:] @app.get('/api/health') def health(): return jsonify({'ok': True}) @app.get('/api/runtime/listen_only') def get_listen_only(): return jsonify({'ok': True, 'listen_only': is_listen_only()}) @app.post('/api/runtime/listen_only') def set_listen_only_mode(): body = request.get_json(silent=True) or {} if "enabled" not in body: return jsonify({'ok': False, 'error': 'enabled required'}), 400 enabled = bool(body.get("enabled")) current = set_listen_only(enabled) logger.info('[运行时] listen_only=%s', current) return jsonify({'ok': True, 'listen_only': current}) @app.post('/api/task/receive') def receive_task(): payload = request.get_json(silent=True) or {} task_id = tm.create_task(payload) logger.info('[任务] receive task_id=%s', task_id) return jsonify({'ok': True, 'task_id': task_id}) @app.post('/api/task/cancel') def cancel_task(): body = request.get_json(silent=True) or {} task_id = str(body.get('task_id', '')).strip() if not task_id: return jsonify({'ok': False, 'error': 'task_id required'}), 400 ok = tm.cancel_task(task_id) return jsonify({'ok': ok, 'task_id': task_id}) @app.get('/api/task/status/') def task_status(task_id: str): task = tm.get_task(task_id) if not task: return jsonify({'ok': False, 'error': 'not found'}), 404 return jsonify({'ok': True, 'task': task}) @app.get('/api/task/list') def task_list(): limit = int(request.args.get('limit', 100)) return jsonify({'ok': True, 'tasks': tm.list_tasks(limit=limit)}) @app.get('/debug/simulator') def debug_simulator(): return """ 链路测试页

AI链路模拟测试

店铺acc_id
客户customer_id
商品标题
订单信息
客户消息
选项
等待发送...
""" @app.post('/api/simulate/reset') def sim_reset(): body = request.get_json(silent=True) or {} acc_id = str(body.get("acc_id", "")).strip() customer_id = str(body.get("customer_id", "")).strip() if not acc_id or not customer_id: return jsonify({'ok': False, 'error': 'acc_id and customer_id required'}), 400 key = f"{acc_id}:{customer_id}" sim_pending_images.pop(key, None) sim_recent_dialogue.pop(key, None) try: sim_orch.store.upsert_session(key, acc_id, customer_id, "pre_sales", {"after_sales_stage": "new"}) except Exception: pass return jsonify({'ok': True, 'customer_key': key, 'message': '上下文已清空'}) @app.post('/api/simulate/message') def sim_message(): body = request.get_json(silent=True) or {} acc_id = str(body.get("acc_id", "")).strip() customer_id = str(body.get("customer_id", "")).strip() goods_name = str(body.get("goods_name", "")).strip() goods_order = str(body.get("goods_order", "")).strip() msg = str(body.get("msg", "")).strip() simulate_draw = bool(body.get("simulate_draw")) if not acc_id or not customer_id or not msg: return jsonify({'ok': False, 'error': 'acc_id, customer_id, msg required'}), 400 key = f"{acc_id}:{customer_id}" urls = extract_image_urls(msg) for u in urls: if u not in sim_pending_images[key]: sim_pending_images[key].append(u) _sim_append_dialogue(key, "user", msg) latest_image_url = urls[-1] if urls else (sim_pending_images[key][-1] if sim_pending_images[key] else "") context = { "customer_key": key, "acc_id": acc_id, "customer_id": customer_id, "goods_name": goods_name, "goods_order": goods_order, "msg": msg, "intent": "unknown", "pending_images": len(sim_pending_images[key]), "pending_image_urls": sim_pending_images[key][-5:], "current_image_urls": urls[-3:], "latest_image_url": latest_image_url, "auto_quote_trigger": False, "last_reply": "", "recent_dialogue": sim_recent_dialogue[key][-12:], } route, decision, state = asyncio.run(sim_orch.decide(context)) if decision.reply: _sim_append_dialogue(key, "assistant", decision.reply) draw_result: dict = {} if simulate_draw and decision.action == "quote" and latest_image_url: draw_result = asyncio.run( auto_draw_preview( image_url=latest_image_url, customer_id=customer_id, requirement=msg, ) ) logger.info("[模拟] key=%s route=%s action=%s", key, route, decision.action) return jsonify( { "ok": True, "customer_key": key, "route": route, "decision": { "action": decision.action, "reply": decision.reply, "transfer_msg": decision.transfer_msg, "quote_mode": decision.quote_mode, "reason": decision.reason, }, "state": state, "pending_images": sim_pending_images[key], "draw_result": draw_result, } ) return app def run_http_server(host: str, port: int, task_manager: TaskManager | None = None) -> None: app = create_http_app(task_manager=task_manager) app.run(host=host, port=port, debug=False, use_reloader=False)