from datetime import datetime from flask import jsonify, request, send_from_directory from app.application.bot_controller import bot_controller from app.infrastructure.service.backend.config import ASSETS_DIR, STATIC_DIR from app.infrastructure.service.backend.db import SQLITE_DB_PATH, get_conn, get_setting, set_setting from app.infrastructure.service.logging.log_service import log_event, new_trace_id LOCAL_SETTING_DEFAULTS = { "auto_reply_enabled": "1", "listener_enabled": "0", "listener_runtime_status": "stopped", "full_auto_reply_enabled": "0", "reply_fallback_mode": "ai", } LOCAL_SETTING_CACHE = {} def _load_local_settings(force=False): if LOCAL_SETTING_CACHE and not force: return for key, default in LOCAL_SETTING_DEFAULTS.items(): LOCAL_SETTING_CACHE[key] = str(get_setting(key, default)) def _get_local_setting(key, default=None): _load_local_settings(force=True) return LOCAL_SETTING_CACHE.get(key, default) def _set_local_setting(key, value): v = str(value) LOCAL_SETTING_CACHE[key] = v set_setting(key, v) def _frontend_index_available(): return STATIC_DIR != ASSETS_DIR and (STATIC_DIR / "index.html").exists() def register_rule_routes(app): @app.route("/") @app.route("/index.html") def frontend_page(): if _frontend_index_available(): return send_from_directory(STATIC_DIR, "index.html") return send_from_directory(ASSETS_DIR, "admin.html") @app.route("/admin.html") def admin_page(): return send_from_directory(ASSETS_DIR, "admin.html") @app.route("/api/rules", methods=["GET", "POST"]) def api_rules(): action = request.values.get("action", "list") conn = get_conn() now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") try: with conn.cursor() as cur: if action == "list": cur.execute("SELECT * FROM auto_reply_rules ORDER BY id DESC") rows = cur.fetchall() log_event("INFO", "api", "api.rules.list", new_trace_id("rules"), "query", "ok", "查询回复匹配配置", extra={"db_path": str(SQLITE_DB_PATH), "count": len(rows)}) return jsonify({"success": True, "data": rows, "db_path": str(SQLITE_DB_PATH)}) if action == "create": keyword = (request.values.get("keyword") or "").strip() match_type = request.values.get("match_type", "contain") reply_text = (request.values.get("reply_text") or "").strip() is_active = int(request.values.get("is_active", 1)) if not keyword or not reply_text: return jsonify({"success": False, "message": "关键词和回复内容不能为空"}) if match_type not in ["contain", "equal"]: match_type = "contain" cur.execute("INSERT INTO auto_reply_rules(keyword, match_type, reply_text, is_active, created_at, updated_at) VALUES (%s, %s, %s, %s, %s, %s)", (keyword, match_type, reply_text, is_active, now, now)) conn.commit() return jsonify({"success": True}) if action == "toggle": rule_id = int(request.values.get("id", 0)) is_active = int(request.values.get("is_active", 0)) if rule_id <= 0: return jsonify({"success": False, "message": "参数错误"}) cur.execute("UPDATE auto_reply_rules SET is_active = %s, updated_at = %s WHERE id = %s", (is_active, now, rule_id)) conn.commit() return jsonify({"success": True}) if action == "delete": rule_id = int(request.values.get("id", 0)) if rule_id <= 0: return jsonify({"success": False, "message": "参数错误"}) cur.execute("DELETE FROM auto_reply_rules WHERE id = %s", (rule_id,)) conn.commit() return jsonify({"success": True}) if action == "settings_get": auto_on = _get_local_setting("auto_reply_enabled", "1") == "1" full_auto_on = _get_local_setting("full_auto_reply_enabled", "0") == "1" reply_fallback_mode = (_get_local_setting("reply_fallback_mode", "ai") or "ai").strip() or "ai" bot_status = bot_controller.status() runtime_status = (bot_status.get("status") or "stopped").strip().lower() if runtime_status not in ["running", "starting", "stopping", "stopped", "error"]: runtime_status = "stopped" listener_on = runtime_status in ["running", "starting"] return jsonify({ "success": True, "auto_reply_enabled": auto_on, "listener_enabled": listener_on, "listener_runtime_status": runtime_status, "listener_intent_enabled": _get_local_setting("listener_enabled", "0") == "1", "full_auto_reply_enabled": full_auto_on, "reply_fallback_mode": reply_fallback_mode, }) if action == "settings_set": if "auto_reply_enabled" in request.values: auto_on = "1" if request.values.get("auto_reply_enabled", "1") == "1" else "0" _set_local_setting("auto_reply_enabled", auto_on) if "listener_enabled" in request.values: listener_on = "1" if request.values.get("listener_enabled", "0") == "1" else "0" _set_local_setting("listener_enabled", listener_on) if "listener_runtime_status" in request.values: runtime_status = (request.values.get("listener_runtime_status") or "stopped").strip().lower() if runtime_status not in ["running", "starting", "stopping", "stopped", "error"]: runtime_status = "stopped" _set_local_setting("listener_runtime_status", runtime_status) if "full_auto_reply_enabled" in request.values: full_auto_on = "1" if request.values.get("full_auto_reply_enabled", "0") == "1" else "0" _set_local_setting("full_auto_reply_enabled", full_auto_on) if "reply_fallback_mode" in request.values: reply_fallback_mode = (request.values.get("reply_fallback_mode") or "ai").strip() or "ai" _set_local_setting("reply_fallback_mode", reply_fallback_mode) return jsonify({"success": True}) if action == "messages_recent": limit = int(request.values.get("limit", 50)) limit = max(1, min(100, limit)) cur.execute("SELECT * FROM messages ORDER BY id DESC LIMIT %s", (limit,)) rows = cur.fetchall() return jsonify({"success": True, "data": rows}) return jsonify({"success": False, "message": "未知操作"}) finally: conn.close()