423 lines
17 KiB
Python
423 lines
17 KiB
Python
"""
|
||||
|
|
日记整理命令
|
|||
|
|
负责协调各个 Skill 完成日记的分析和整理
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from datetime import datetime
|
|||
|
|
from pathlib import Path
|
|||
|
|
from typing import Dict, Any, Optional, List
|
|||
|
|
|
|||
|
|
# Handle imports with both relative and absolute paths
|
|||
|
|
try:
|
|||
|
|
from ..agent_core import Command, SkillResult, CommandContext
|
|||
|
|
from ..date_validation import validate_date_input
|
|||
|
|
from ..input_validation import command_input_validator
|
|||
|
|
from ..config_validation import ClaudeAPIConfig
|
|||
|
|
from ..skills.claude_skill import ClaudeAnalyzeSkill, ClaudeTransformSkill
|
|||
|
|
from ..skills.obsidian_skill import (
|
|||
|
|
ObsidianReadSkill,
|
|||
|
|
ObsidianWriteSkill,
|
|||
|
|
ObsidianAppendSkill,
|
|||
|
|
ObsidianListFilesSkill,
|
|||
|
|
)
|
|||
|
|
except ImportError:
|
|||
|
|
# Fallback to absolute imports when running as script
|
|||
|
|
from agent_core import Command, SkillResult, CommandContext
|
|||
|
|
from date_validation import validate_date_input
|
|||
|
|
from input_validation import command_input_validator
|
|||
|
|
from config_validation import ClaudeAPIConfig
|
|||
|
|
from skills.claude_skill import ClaudeAnalyzeSkill, ClaudeTransformSkill
|
|||
|
|
from skills.obsidian_skill import (
|
|||
|
|
ObsidianReadSkill,
|
|||
|
|
ObsidianWriteSkill,
|
|||
|
|
ObsidianAppendSkill,
|
|||
|
|
ObsidianListFilesSkill,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class OrganizeCommand(Command):
|
|||
|
|
"""日记整理命令"""
|
|||
|
|
|
|||
|
|
def __init__(self) -> None:
|
|||
|
|
super().__init__(
|
|||
|
|
name="organize",
|
|||
|
|
description="分析和整理日记内容,提取经验和要点",
|
|||
|
|
aliases=["org", "organize-journal"],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 注册 Skills
|
|||
|
|
self.register_skill(ObsidianReadSkill())
|
|||
|
|
self.register_skill(ObsidianWriteSkill())
|
|||
|
|
self.register_skill(ObsidianAppendSkill())
|
|||
|
|
self.register_skill(ClaudeAnalyzeSkill())
|
|||
|
|
self.register_skill(ClaudeTransformSkill())
|
|||
|
|
|
|||
|
|
async def execute(self, context: CommandContext) -> SkillResult:
|
|||
|
|
"""
|
|||
|
|
执行日记整理命令
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
context: 命令执行上下文
|
|||
|
|
- args:
|
|||
|
|
- date: 日期(格式: YYYY-MM-DD,默认今天)
|
|||
|
|
- vault_path: Obsidian vault 路径
|
|||
|
|
- daily_folder: 日记文件夹(默认: Daily)
|
|||
|
|
- config: 系统配置
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
SkillResult: 执行结果
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
# Validate and sanitize input arguments
|
|||
|
|
validated_args = command_input_validator.validate_organize_command_input(
|
|||
|
|
context.args
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 获取参数
|
|||
|
|
date_str: Optional[str] = validated_args.get("date")
|
|||
|
|
vault_path: Optional[str] = validated_args.get("vault_path")
|
|||
|
|
daily_folder: str = validated_args.get("daily_folder", "Daily")
|
|||
|
|
|
|||
|
|
# 从配置中获取信息
|
|||
|
|
config: Dict[str, Any] = context.config or {}
|
|||
|
|
obsidian_config: Dict[str, Any] = config.get("obsidian", {})
|
|||
|
|
claude_config: Dict[str, Any] = config.get("claude", {})
|
|||
|
|
output_config: Dict[str, Any] = config.get("output", {})
|
|||
|
|
analysis_config: Dict[str, Any] = config.get("analysis", {})
|
|||
|
|
|
|||
|
|
# 如果没有提供日期,使用今天
|
|||
|
|
if not date_str:
|
|||
|
|
date_str = datetime.now().strftime("%Y-%m-%d")
|
|||
|
|
else:
|
|||
|
|
# Validate the provided date
|
|||
|
|
validated_date = validate_date_input(
|
|||
|
|
date_str,
|
|||
|
|
field_name="date",
|
|||
|
|
required=True,
|
|||
|
|
format_hint="iso_date"
|
|||
|
|
)
|
|||
|
|
date_str = validated_date.strftime("%Y-%m-%d")
|
|||
|
|
|
|||
|
|
# 获取必要的配置
|
|||
|
|
api_url: str = obsidian_config.get("rest_api", {}).get(
|
|||
|
|
"url", "https://localhost:27123"
|
|||
|
|
)
|
|||
|
|
api_key: Optional[str] = obsidian_config.get("rest_api", {}).get("api_key")
|
|||
|
|
|
|||
|
|
# Create enhanced Claude API configuration
|
|||
|
|
try:
|
|||
|
|
claude_api_config = ClaudeAPIConfig(**claude_config)
|
|||
|
|
except Exception as e:
|
|||
|
|
return SkillResult(
|
|||
|
|
success=False,
|
|||
|
|
error=f"Claude API 配置无效: {str(e)}",
|
|||
|
|
message="请检查 Claude API 配置",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if not api_key or not claude_api_config.api_key:
|
|||
|
|
return SkillResult(
|
|||
|
|
success=False,
|
|||
|
|
error="缺少必要的配置",
|
|||
|
|
message="请配置 Obsidian API 密钥和 Claude API 密钥",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
self.logger.info(f"开始整理日期 {date_str} 的日记")
|
|||
|
|
|
|||
|
|
# 步骤 1: 读取日记
|
|||
|
|
daily_note_path = str(Path(daily_folder) / f"{date_str}.md")
|
|||
|
|
|
|||
|
|
self.logger.info(f"步骤 1: 读取日记 {daily_note_path}")
|
|||
|
|
read_skill = self.skills["obsidian_read"]
|
|||
|
|
read_result: SkillResult = await read_skill.execute(
|
|||
|
|
context,
|
|||
|
|
file_path=daily_note_path,
|
|||
|
|
vault_path=vault_path,
|
|||
|
|
api_url=api_url,
|
|||
|
|
api_key=api_key,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if not read_result.success:
|
|||
|
|
return read_result
|
|||
|
|
|
|||
|
|
journal_content: str = read_result.data["content"]
|
|||
|
|
|
|||
|
|
# 步骤 2: 使用 Claude 分析日记
|
|||
|
|
self.logger.info("步骤 2: 使用 Claude 分析日记内容")
|
|||
|
|
analyze_skill = self.skills["claude_analyze"]
|
|||
|
|
categories: List[str] = analysis_config.get("categories", [])
|
|||
|
|
|
|||
|
|
analyze_result: SkillResult = await analyze_skill.execute(
|
|||
|
|
context,
|
|||
|
|
journal_content=journal_content,
|
|||
|
|
claude_config=claude_api_config,
|
|||
|
|
categories=categories,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if not analyze_result.success:
|
|||
|
|
return analyze_result
|
|||
|
|
|
|||
|
|
analysis_data: Dict[str, Any] = analyze_result.data["analysis"]
|
|||
|
|
|
|||
|
|
# 步骤 3: 整理分析结果到各个位置
|
|||
|
|
self.logger.info("步骤 3: 整理分析结果")
|
|||
|
|
|
|||
|
|
write_results: Dict[str, SkillResult] = {}
|
|||
|
|
write_skill = self.skills["obsidian_write"]
|
|||
|
|
|
|||
|
|
# 处理经验
|
|||
|
|
if "experiences" in analysis_data and analysis_data["experiences"]:
|
|||
|
|
experiences_content: str = self._format_experiences(
|
|||
|
|
analysis_data["experiences"], date_str
|
|||
|
|
)
|
|||
|
|
experiences_path = str(
|
|||
|
|
Path(
|
|||
|
|
output_config.get("experiences_folder", "Knowledge/Experiences")
|
|||
|
|
)
|
|||
|
|
/ f"{date_str}.md"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
exp_result: SkillResult = await write_skill.execute(
|
|||
|
|
context,
|
|||
|
|
file_path=experiences_path,
|
|||
|
|
content=experiences_content,
|
|||
|
|
api_url=api_url,
|
|||
|
|
api_key=api_key,
|
|||
|
|
overwrite=True,
|
|||
|
|
)
|
|||
|
|
write_results["experiences"] = exp_result
|
|||
|
|
|
|||
|
|
# 处理经验教训
|
|||
|
|
if "lessons_learned" in analysis_data and analysis_data["lessons_learned"]:
|
|||
|
|
lessons_content: str = self._format_lessons(
|
|||
|
|
analysis_data["lessons_learned"], date_str
|
|||
|
|
)
|
|||
|
|
lessons_path = str(
|
|||
|
|
Path(output_config.get("lessons_folder", "Knowledge/Lessons"))
|
|||
|
|
/ f"{date_str}.md"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
lessons_result: SkillResult = await write_skill.execute(
|
|||
|
|
context,
|
|||
|
|
file_path=lessons_path,
|
|||
|
|
content=lessons_content,
|
|||
|
|
api_url=api_url,
|
|||
|
|
api_key=api_key,
|
|||
|
|
overwrite=True,
|
|||
|
|
)
|
|||
|
|
write_results["lessons"] = lessons_result
|
|||
|
|
|
|||
|
|
# 处理待办事项
|
|||
|
|
if "action_items" in analysis_data and analysis_data["action_items"]:
|
|||
|
|
tasks_content: str = self._format_tasks(
|
|||
|
|
analysis_data["action_items"], date_str
|
|||
|
|
)
|
|||
|
|
tasks_path = str(
|
|||
|
|
Path(output_config.get("tasks_folder", "Tasks/Daily"))
|
|||
|
|
/ f"{date_str}.md"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
tasks_result: SkillResult = await write_skill.execute(
|
|||
|
|
context,
|
|||
|
|
file_path=tasks_path,
|
|||
|
|
content=tasks_content,
|
|||
|
|
api_url=api_url,
|
|||
|
|
api_key=api_key,
|
|||
|
|
overwrite=True,
|
|||
|
|
)
|
|||
|
|
write_results["tasks"] = tasks_result
|
|||
|
|
|
|||
|
|
# 处理问题
|
|||
|
|
if "problems" in analysis_data and analysis_data["problems"]:
|
|||
|
|
problems_content: str = self._format_problems(
|
|||
|
|
analysis_data["problems"], date_str
|
|||
|
|
)
|
|||
|
|
problems_path = str(
|
|||
|
|
Path(output_config.get("problems_folder", "Knowledge/Problems"))
|
|||
|
|
/ f"{date_str}.md"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
problems_result: SkillResult = await write_skill.execute(
|
|||
|
|
context,
|
|||
|
|
file_path=problems_path,
|
|||
|
|
content=problems_content,
|
|||
|
|
api_url=api_url,
|
|||
|
|
api_key=api_key,
|
|||
|
|
overwrite=True,
|
|||
|
|
)
|
|||
|
|
write_results["problems"] = problems_result
|
|||
|
|
|
|||
|
|
# 处理成就
|
|||
|
|
if "achievements" in analysis_data and analysis_data["achievements"]:
|
|||
|
|
achievements_content: str = self._format_achievements(
|
|||
|
|
analysis_data["achievements"], date_str
|
|||
|
|
)
|
|||
|
|
achievements_path = str(
|
|||
|
|
Path(
|
|||
|
|
output_config.get(
|
|||
|
|
"achievements_folder", "Knowledge/Achievements"
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
/ f"{date_str}.md"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
achievements_result: SkillResult = await write_skill.execute(
|
|||
|
|
context,
|
|||
|
|
file_path=achievements_path,
|
|||
|
|
content=achievements_content,
|
|||
|
|
api_url=api_url,
|
|||
|
|
api_key=api_key,
|
|||
|
|
overwrite=True,
|
|||
|
|
)
|
|||
|
|
write_results["achievements"] = achievements_result
|
|||
|
|
|
|||
|
|
# 处理改进建议
|
|||
|
|
if "improvements" in analysis_data and analysis_data["improvements"]:
|
|||
|
|
improvements_content: str = self._format_improvements(
|
|||
|
|
analysis_data["improvements"], date_str
|
|||
|
|
)
|
|||
|
|
improvements_path = str(
|
|||
|
|
Path(
|
|||
|
|
output_config.get(
|
|||
|
|
"improvements_folder", "Knowledge/Improvements"
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
/ f"{date_str}.md"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
improvements_result: SkillResult = await write_skill.execute(
|
|||
|
|
context,
|
|||
|
|
file_path=improvements_path,
|
|||
|
|
content=improvements_content,
|
|||
|
|
api_url=api_url,
|
|||
|
|
api_key=api_key,
|
|||
|
|
overwrite=True,
|
|||
|
|
)
|
|||
|
|
write_results["improvements"] = improvements_result
|
|||
|
|
|
|||
|
|
# 统计结果
|
|||
|
|
successful_writes: int = sum(1 for r in write_results.values() if r.success)
|
|||
|
|
|
|||
|
|
return SkillResult(
|
|||
|
|
success=True,
|
|||
|
|
data={
|
|||
|
|
"date": date_str,
|
|||
|
|
"journal_file": daily_note_path,
|
|||
|
|
"analysis": analysis_data,
|
|||
|
|
"write_results": {k: v.success for k, v in write_results.items()},
|
|||
|
|
"successful_writes": successful_writes,
|
|||
|
|
"total_writes": len(write_results),
|
|||
|
|
"organized_at": datetime.now().isoformat(),
|
|||
|
|
},
|
|||
|
|
message=f"成功整理日记 {date_str},已生成 {successful_writes} 个文件",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
self.logger.error(f"执行日记整理命令时出错: {str(e)}")
|
|||
|
|
return SkillResult(success=False, error=str(e), message="日记整理异常")
|
|||
|
|
|
|||
|
|
def _format_experiences(
|
|||
|
|
self, experiences: List[Dict[str, Any]], date_str: str
|
|||
|
|
) -> str:
|
|||
|
|
"""格式化经验内容"""
|
|||
|
|
content: str = f"# 经验总结 - {date_str}\n\n"
|
|||
|
|
content += f"*生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n"
|
|||
|
|
|
|||
|
|
for i, exp in enumerate(experiences, 1):
|
|||
|
|
content += f"## {i}. {exp.get('title', '经验')}\n\n"
|
|||
|
|
content += f"**分类**: {exp.get('category', '未分类')}\n"
|
|||
|
|
content += f"**优先级**: {exp.get('priority', 'medium')}\n\n"
|
|||
|
|
content += f"{exp.get('content', '')}\n\n"
|
|||
|
|
|
|||
|
|
content += f"\n---\n*来源: [[{date_str}]]*\n"
|
|||
|
|
return content
|
|||
|
|
|
|||
|
|
def _format_lessons(self, lessons: List[Dict[str, Any]], date_str: str) -> str:
|
|||
|
|
"""格式化经验教训内容"""
|
|||
|
|
content: str = f"# 经验教训 - {date_str}\n\n"
|
|||
|
|
content += f"*生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n"
|
|||
|
|
|
|||
|
|
for i, lesson in enumerate(lessons, 1):
|
|||
|
|
content += f"## {i}. {lesson.get('lesson', '教训')}\n\n"
|
|||
|
|
content += f"**背景**: {lesson.get('context', '')}\n\n"
|
|||
|
|
content += f"**应用**: {lesson.get('application', '')}\n\n"
|
|||
|
|
|
|||
|
|
content += f"\n---\n*来源: [[{date_str}]]*\n"
|
|||
|
|
return content
|
|||
|
|
|
|||
|
|
def _format_tasks(self, tasks: List[Dict[str, Any]], date_str: str) -> str:
|
|||
|
|
"""格式化待办事项内容"""
|
|||
|
|
content: str = f"# 待办事项 - {date_str}\n\n"
|
|||
|
|
content += f"*生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n"
|
|||
|
|
|
|||
|
|
# 按优先级分组
|
|||
|
|
by_priority: Dict[str, List[Dict[str, Any]]] = {
|
|||
|
|
"high": [],
|
|||
|
|
"medium": [],
|
|||
|
|
"low": [],
|
|||
|
|
}
|
|||
|
|
for task in tasks:
|
|||
|
|
priority: str = task.get("priority", "medium")
|
|||
|
|
by_priority[priority].append(task)
|
|||
|
|
|
|||
|
|
for priority in ["high", "medium", "low"]:
|
|||
|
|
if by_priority[priority]:
|
|||
|
|
priority_text: Dict[str, str] = {
|
|||
|
|
"high": "🔴 高",
|
|||
|
|
"medium": "� 中",
|
|||
|
|
r"low": "🟢 低",
|
|||
|
|
}
|
|||
|
|
content += f"## {priority_text[priority]} 优先级\n\n"
|
|||
|
|
|
|||
|
|
for task in by_priority[priority]:
|
|||
|
|
content += f"- [ ] {task.get('task', '任务')}\n"
|
|||
|
|
if task.get("deadline"):
|
|||
|
|
content += f" - 截止: {task.get('deadline')}\n"
|
|||
|
|
content += "\n"
|
|||
|
|
|
|||
|
|
content += f"\n---\n*来源: [[{date_str}]]*\n"
|
|||
|
|
return content
|
|||
|
|
|
|||
|
|
def _format_problems(self, problems: List[Dict[str, Any]], date_str: str) -> str:
|
|||
|
|
"""格式化问题内容"""
|
|||
|
|
content: str = f"# 问题记录 - {date_str}\n\n"
|
|||
|
|
content += f"*生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n"
|
|||
|
|
|
|||
|
|
for i, problem in enumerate(problems, 1):
|
|||
|
|
content += f"## {i}. {problem.get('problem', '问题')}\n\n"
|
|||
|
|
content += f"**影响**: {problem.get('impact', '')}\n\n"
|
|||
|
|
content += f"**建议方案**: {problem.get('proposed_solution', '')}\n\n"
|
|||
|
|
|
|||
|
|
content += f"\n---\n*来源: [[{date_str}]]*\n"
|
|||
|
|
return content
|
|||
|
|
|
|||
|
|
def _format_achievements(
|
|||
|
|
self, achievements: List[Dict[str, Any]], date_str: str
|
|||
|
|
) -> str:
|
|||
|
|
"""格式化成就内容"""
|
|||
|
|
content: str = f"# 成就记录 - {date_str}\n\n"
|
|||
|
|
content += f"*生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n"
|
|||
|
|
|
|||
|
|
for i, achievement in enumerate(achievements, 1):
|
|||
|
|
content += f"## {i}. {achievement.get('achievement', '成就')}\n\n"
|
|||
|
|
content += f"**重要性**: {achievement.get('significance', '')}\n\n"
|
|||
|
|
content += f"**证据**: {achievement.get('evidence', '')}\n\n"
|
|||
|
|
|
|||
|
|
content += f"\n---\n*来源: [[{date_str}]]*\n"
|
|||
|
|
return content
|
|||
|
|
|
|||
|
|
def _format_improvements(
|
|||
|
|
self, improvements: List[Dict[str, Any]], date_str: str
|
|||
|
|
) -> str:
|
|||
|
|
"""格式化改进建议内容"""
|
|||
|
|
content: str = f"# 改进建议 - {date_str}\n\n"
|
|||
|
|
content += f"*生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n"
|
|||
|
|
|
|||
|
|
for i, improvement in enumerate(improvements, 1):
|
|||
|
|
content += f"## {i}. {improvement.get('area', '改进领域')}\n\n"
|
|||
|
|
content += f"**当前状态**: {improvement.get('current_state', '')}\n\n"
|
|||
|
|
content += f"**建议改进**: {improvement.get('suggested_change', '')}\n\n"
|
|||
|
|
content += f"**预期收益**: {improvement.get('expected_benefit', '')}\n\n"
|
|||
|
|
|
|||
|
|
content += f"\n---\n*来源: [[{date_str}]]*\n"
|
|||
|
|
return content
|