Files
journal_organizer/commands/organize_command.py
T
windyboy f7e54692a9 Initial project setup: Obsidian intelligent journal organizer
- Add core agent architecture with Command + Skill pattern
- Implement Claude API integration for content analysis
- Add Obsidian REST API integration for vault operations
- Create conversational interface (v2.0) with natural language processing
- Add comprehensive configuration management and validation
- Include project documentation and developer guides
- Set up testing framework with unit, integration, and property tests
- Add Kiro specs for Claude API configuration and code quality improvements
- Configure project steering files for development guidelines
2025-12-31 17:55:10 +08:00

423 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
日记整理命令
负责协调各个 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