"""Daily digest analyzer - fetches today's memos and generates AI summary.""" import json import logging import os import sys from datetime import datetime, timezone # Add parent dir to path for local imports sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from tools.config import load_secrets, get_output_dir from tools.llm import DeepSeekClient from tools.memos_client import MemosClient from tools.formatter import format_daily_digest logger = logging.getLogger(__name__) # System prompt for daily digest DAILY_SYSTEM_PROMPT = """你是一个灵感整理助手。你的任务是将用户零散的灵感记录进行智能整理。 请严格按照要求整理,输出纯 JSON(不要代码块标记,不要额外说明): { "summary": "一段简短的今日灵感总结,50字以内", "categories": { "分类名1": ["具体灵感项1", "具体灵感项2"], "分类名2": ["具体灵感项3"] }, "connections": ["跨主题关联发现1", "2-3条"], "todos": ["待办事项1", "2-3条"] } 分类原则:根据内容自然归类,分类名不超过4个字。 关联发现:识别不同灵感之间的关联、冲突或可串联的主题。 待办:从灵感中提取可执行的事项。""" def parse_ai_response(text): """Parse AI response, handling both pure JSON and code-block wrapped JSON.""" text = text.strip() # Remove markdown code block wrappers if present if text.startswith("```"): lines = text.split("\n") # Remove first and last ``` lines if lines[0].startswith("```"): lines = lines[1:] if lines and lines[-1].strip() == "```": lines = lines[:-1] text = "\n".join(lines).strip() return json.loads(text) def run(memos_client, llm_client, date=None): """Run daily digest analysis.""" date = date or datetime.now(timezone.utc) logger.info("Daily digest started for %s", date.strftime("%Y-%m-%d")) # Step 1: Fetch today's memos memos = memos_client.list_memos(days=1) if not memos: logger.info("No memos today, skipping") # Still generate a minimal file content = format_daily_digest( date=date, categories={}, summary="今天没有记录灵感。", connections=[], todos=[] ) output_dir = get_output_dir("daily") filename = f"{date.strftime('%Y-%m-%d')}_digest.md" filepath = os.path.join(output_dir, filename) with open(filepath, "w", encoding="utf-8") as f: f.write(content) logger.info("Empty digest written to %s", filepath) return filepath, 0 # Step 2: Prepare prompt memo_texts = [] for m in memos: time_str = m["created_at"][:16].replace("T", " ") memo_texts.append(f"[{time_str}] {m['content']}") user_prompt = f"请分析以下灵感记录:\n\n" + "\n".join(memo_texts) # Step 3: Call DeepSeek API raw_response = llm_client.ask( system_prompt=DAILY_SYSTEM_PROMPT, user_prompt=user_prompt, temperature=0.3 ) # Step 4: Parse and format try: data = parse_ai_response(raw_response) except (json.JSONDecodeError, KeyError) as e: logger.error("Failed to parse AI response: %s", e) logger.error("Raw response: %s", raw_response[:200]) data = { "summary": f"AI 解析失败,请查看原始 Memos。", "categories": {"未分类": [m["content"] for m in memos]}, "connections": [], "todos": [] } categories = data.get("categories", {}) connections = data.get("connections", []) todos = data.get("todos", []) summary = data.get("summary", "") # Step 5: Format and write output content = format_daily_digest( date=date, categories=categories, summary=summary, connections=connections, todos=todos ) output_dir = get_output_dir("daily") filename = f"{date.strftime('%Y-%m-%d')}_digest.md" filepath = os.path.join(output_dir, filename) with open(filepath, "w", encoding="utf-8") as f: f.write(content) logger.info( "Daily digest written to %s | %d memos | %d categories", filepath, len(memos), len(categories) ) return filepath, len(memos) def main(): """CLI entry point.""" logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s" ) try: secrets = load_secrets() except FileNotFoundError as e: print(e) sys.exit(1) memos_client = MemosClient( base_url=secrets.get("memos_url", "http://localhost:5230"), access_token=secrets["memos_token"] ) llm_client = DeepSeekClient( api_key=secrets["deepseek_api_key"], model=secrets.get("deepseek_model", "deepseek-chat") ) filepath, count = run(memos_client, llm_client) print(f"Done: {filepath} ({count} memos)") if __name__ == "__main__": main()