import re from typing import Any from fastapi import HTTPException from sqlalchemy.orm import Session from app.application.feishu.delivery import send_text_if_configured from app.application.feishu.results import command_result from app.core.config import get_settings from app.modules.ai_memory.constants import AIMemoryStatus from app.modules.ai_memory.service import AIMemoryService from app.modules.feishu.constants import FeishuCommandName, FeishuReplyType from app.modules.feishu.service import FeishuService RULE_TITLE = "AI 学习规则" RULE_CREATE_PATTERN = re.compile(r"^学习规则(?:\s+(\d{1,3}))?\s*[::]\s*(.*)$") MARKET_RULE_CREATE_PATTERN = re.compile( r"^学习市场规则(?:\s+(\d{1,3}))?\s*[::]\s*(.*)$" ) RULE_DISABLE_PATTERN = re.compile(r"^停用规则\s+(MEM-[A-Za-z0-9-]+)$", re.IGNORECASE) RULE_ENABLE_PATTERN = re.compile(r"^启用规则\s+(MEM-[A-Za-z0-9-]+)$", re.IGNORECASE) RULE_LIST_COMMANDS = {"查看规则", "规则列表", "查看市场规则"} RULE_COMMAND_PREFIXES = ( "学习市场规则", "学习规则", "查看市场规则", "查看规则", "规则列表", "停用规则", "启用规则", ) RULE_COMMAND_HELP = ( "规则指令格式:\n" "学习规则:<规则内容>\n" "学习规则 80:<规则内容>\n" "学习市场规则 80:<仅用于市场分析的规则内容>\n" "查看规则\n" "停用规则 <规则编号>\n" "启用规则 <规则编号>" ) def handle_rule_command( db: Session, feishu: FeishuService, command_text: str, chat_id: str | None, actor: str, auto_reply: bool, ) -> dict[str, Any] | None: """Handle persistent AI rule commands.""" if not command_text.startswith(RULE_COMMAND_PREFIXES): return None command = _command_name(command_text) if command in { FeishuCommandName.RULE_CREATE, FeishuCommandName.RULE_DISABLE, FeishuCommandName.RULE_ENABLE, } and get_settings().read_only_mode: return _result( feishu, command, "当前为只读模式,不能新增或修改学习规则。请由管理员启用操作后重试。", chat_id, actor, auto_reply, ) content = RULE_COMMAND_HELP try: command, content = _execute(db, command_text, command, actor) except HTTPException as exc: detail = str(exc.detail) if "secret-like" in detail: content = "规则疑似包含密码、令牌或其他密钥信息,已拒绝学习。" elif exc.status_code == 404: content = "没有找到该规则,请先发送“查看规则”确认规则编号。" elif "priority" in detail: content = "规则优先级必须在 1 到 100 之间。" else: content = "规则未保存,请检查指令内容后重试。" return _result(feishu, command, content, chat_id, actor, auto_reply) def _execute( db: Session, command_text: str, command: FeishuCommandName, actor: str, ) -> tuple[FeishuCommandName, str]: market_create_match = MARKET_RULE_CREATE_PATTERN.fullmatch(command_text) create_match = market_create_match or RULE_CREATE_PATTERN.fullmatch(command_text) disable_match = RULE_DISABLE_PATTERN.fullmatch(command_text) enable_match = RULE_ENABLE_PATTERN.fullmatch(command_text) memory = AIMemoryService(db) if create_match: return command, _create_rule(memory, create_match, market_create_match is not None, actor) if command_text in RULE_LIST_COMMANDS: return FeishuCommandName.RULE_LIST, _list_rules(memory, command_text) if disable_match or enable_match: enabled = enable_match is not None match = enable_match or disable_match rule = memory.update_rule( code=match.group(1), content=None, priority=None, tags=None, enabled=enabled, actor=actor, ) state = "已启用" if enabled else "已停用" return ( FeishuCommandName.RULE_ENABLE if enabled else FeishuCommandName.RULE_DISABLE, f"规则{state}。\n" f"编号:{rule['code']}\n" f"优先级:{rule['importance']}\n" f"范围:{rule['scope']} / {rule['subject']}\n" f"状态:{state}", ) return command, RULE_COMMAND_HELP def _create_rule( memory: AIMemoryService, match: re.Match[str], market_rule: bool, actor: str, ) -> str: priority = int(match.group(1) or 50) content = match.group(2).strip() if not content: return f"规则内容不能为空。\n\n{RULE_COMMAND_HELP}" if not 1 <= priority <= 100: return "规则优先级必须在 1 到 100 之间。" rule = memory.create_rule( content=content, scope="market" if market_rule else "global", subject="market" if market_rule else "company", priority=priority, tags=["feishu", *(["market"] if market_rule else [])], actor=actor, ) return ( "规则已学习。\n" f"编号:{rule['code']}\n" f"优先级:{rule['importance']}\n" f"范围:{rule['scope']} / {rule['subject']}\n" "状态:已启用" ) def _list_rules(memory: AIMemoryService, command_text: str) -> str: rules = memory.list_rules( scope="market" if command_text == "查看市场规则" else None, status_filter=AIMemoryStatus.ACTIVE, limit=20, ) if not rules: return "当前没有已启用的学习规则。" lines = ["当前已启用的学习规则:"] for rule in rules: rule_text = str(rule["content"]) if len(rule_text) > 80: rule_text = f"{rule_text[:80]}…" lines.append( f"{rule['code']}|优先级 {rule['importance']}|" f"{rule['scope']}/{rule['subject']}\n{rule_text}" ) return "\n\n".join(lines) def _command_name(command_text: str) -> FeishuCommandName: if command_text.startswith("停用规则"): return FeishuCommandName.RULE_DISABLE if command_text.startswith("启用规则"): return FeishuCommandName.RULE_ENABLE if command_text.startswith(("查看市场规则", "查看规则", "规则列表")): return FeishuCommandName.RULE_LIST return FeishuCommandName.RULE_CREATE def _result( feishu: FeishuService, command: FeishuCommandName, content: str, chat_id: str | None, actor: str, auto_reply: bool, ) -> dict[str, Any]: response = send_text_if_configured(feishu, chat_id, content, actor) if auto_reply else None return command_result(command, FeishuReplyType.TEXT, RULE_TITLE, content, response)