import json import re from typing import Any from sqlalchemy.orm import Session from app.core.config import get_settings from app.modules.ai_agent.service import AIService from app.modules.feishu.service import FeishuService from app.modules.reports.service import ReportService from app.modules.risk.service import RiskService def _parse_content_text(content: Any) -> str: """Extract plain command text from a Feishu message content payload.""" if isinstance(content, dict): return str(content.get("text") or content.get("content") or "") if not isinstance(content, str): return "" try: data = json.loads(content) except json.JSONDecodeError: return content if isinstance(data, dict): return str(data.get("text") or data.get("content") or "") return content def _clean_command_text(text: str) -> str: """Remove mentions and invisible characters from Feishu command text.""" text = re.sub(r"@\S+", "", text or "") text = text.replace("\u200b", "") return text.strip() class FeishuCommandService: """Route Feishu text commands to reports, risk summaries, or AI replies.""" def __init__(self, db: Session): self.db = db self.feishu = FeishuService(db) def extract_event_command(self, payload: dict[str, Any]) -> dict[str, Any] | None: event = payload.get("event") or {} message = event.get("message") or {} if not message: return None text = _clean_command_text(_parse_content_text(message.get("content"))) if not text: return None sender = event.get("sender") or {} sender_id = sender.get("sender_id") or {} actor = sender_id.get("open_id") or sender_id.get("user_id") or "feishu" return { "text": text, "chat_id": message.get("chat_id"), "actor": actor, } def handle_text( self, text: str, chat_id: str | None = None, actor: str = "feishu", auto_reply: bool = True, ) -> dict[str, Any]: command_text = _clean_command_text(text) lowered = command_text.lower() provider_response: dict[str, Any] | None = None if any(keyword in command_text for keyword in ["日报", "晨报", "经营日报", "经营晨报"]): report = ReportService(self.db).daily_brief() result = { "command": "daily_brief", "reply_type": "card", "title": report["title"], "content": report["content"], "lines": report["lines"], } if auto_reply: provider_response = self._send_card_if_configured( chat_id, report["title"], report["lines"], actor, ) result["provider_response"] = provider_response return result if any(keyword in command_text for keyword in ["周报", "项目周报"]): report = ReportService(self.db).project_weekly() result = { "command": "project_weekly", "reply_type": "card", "title": report["title"], "content": report["content"], "lines": report["lines"], } if auto_reply: provider_response = self._send_card_if_configured( chat_id, report["title"], report["lines"], actor, ) result["provider_response"] = provider_response return result if any(keyword in command_text for keyword in ["打卡", "考勤", "attendance"]): report = ReportService(self.db).attendance_summary() result = { "command": "attendance_summary", "reply_type": "card", "title": report["title"], "content": report["content"], "lines": report["lines"], } if auto_reply: provider_response = self._send_card_if_configured( chat_id, report["title"], report["lines"], actor, ) result["provider_response"] = provider_response return result if any(keyword in command_text for keyword in ["风险", "预警", "risk"]): summary = RiskService(self.db).summary() lines = [ f"- 综合风险等级:{summary['risk_level']}", f"- 风险分:{summary['risk_score']}", f"- 逾期任务:{len(summary['overdue_tasks'])}", f"- 延期项目:{len(summary['delayed_projects'])}", f"- 超预算项目:{len(summary['over_budget_projects'])}", f"- 资金风险账户:{len(summary['fund_risks'])}", f"- 供应商风险:{len(summary['supplier_risks'])}", f"- 打开风险事件:{len(summary['open_events'])}", ] result = { "command": "risk_summary", "reply_type": "card", "title": "风险预警", "content": "\n".join(lines), "lines": lines, } if auto_reply: provider_response = self._send_card_if_configured(chat_id, "风险预警", lines, actor) result["provider_response"] = provider_response return result prompt = command_text for prefix in ["问 ", "ai ", "AI ", "/ask "]: if command_text.startswith(prefix): prompt = command_text[len(prefix) :].strip() break if not prompt: prompt = "请说明你能做什么。" ai_result = AIService(self.db).ask(prompt, context={}, actor=actor, source="feishu") content = ai_result["answer"] is_explicit_ai = lowered.startswith(("ai ", "/ask")) or command_text.startswith("问 ") result = { "command": "ai_ask" if is_explicit_ai else "fallback_ai", "reply_type": "text", "title": "AI 回复", "content": content, } if auto_reply: provider_response = self._send_text_if_configured(chat_id, content, actor) result["provider_response"] = provider_response return result def _send_card_if_configured( self, chat_id: str | None, title: str, lines: list[str], actor: str, ) -> dict[str, Any] | None: settings = get_settings() if not (settings.feishu_app_id and settings.feishu_app_secret): return None card = FeishuService.build_basic_card(title, lines) return self.feishu.send_card(card, receive_id=chat_id, actor=actor) def _send_text_if_configured( self, chat_id: str | None, text: str, actor: str, ) -> dict[str, Any] | None: settings = get_settings() if not (settings.feishu_app_id and settings.feishu_app_secret): return None return self.feishu.send_text(text, receive_id=chat_id, actor=actor)