Files
blackbird/aibox/tools/db_tools.py
T
qing 791fac0efd refactor(aibox): 抽离B批12个工具类至tools包,附58个单测
B批(tools/):迁移依赖子系统的12个工具类(CommandLineTool/SearchSkills/ReloadSkills/InstallSkill/CreateSkill/MemoryTool/MemoTool/UpdateIdentity/UpdateSoul/SaveMemoryAndEndConversation/SetProxy/GetProxy/AskUser),aibox.py减少~1739行

修复MemoryDatabase.get_recent_memories(limit=None)的SQL错误(既有bug,None传给LIMIT ?导致datatype mismatch)

171个单测全绿,循环依赖障碍消除:B批工具直接依赖subsystems而非反向依赖aibox.py
2026-08-13 16:09:31 +08:00

819 lines
32 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.
#!/usr/bin/env python3
"""
tools.db_tools - 数据库相关工具
抽离自 aibox.py,包含 2 个数据库工具:
- MemoryTool : 记忆管理(保存/搜索/查看/删除,支持分页)
- MemoTool : 任务管理(创建/查看/完成/删除/搜索/统计)
依赖:common (constants, Logger) + tools.base.Tool
+ subsystems.db.MemoryDatabase/MemoDatabase
不反向依赖 aibox 任何符号。
"""
import queue
import re
from datetime import datetime, timedelta
from typing import Any, Dict, Optional
from common import constants, Logger
from subsystems import MemoryDatabase, MemoDatabase
from .base import Tool
# ==================== 记忆工具 ====================
class MemoryTool(Tool):
"""记忆工具 - 用于存储、检索和删除对话记忆(支持流式输出,支持分页)"""
def __init__(self, memory_db: MemoryDatabase, logger: Optional[Logger] = None):
super().__init__(logger)
self.memory_db = memory_db
def get_name(self) -> str:
return "memory"
def get_description(self) -> str:
return """记忆管理工具 - 存储、检索和删除重要的对话信息
支持:保存总结、多关键词搜索记忆(支持分页)、查看最近的记忆、删除无用记忆"""
def get_parameters(self) -> Dict:
return {
"type": "object",
"properties": {
"action": {
"type": "string",
"description": "操作类型",
"enum": ["save", "search", "recent", "get", "delete"]
},
"summary": {"type": "string", "description": "要保存的总结内容(用于save操作)"},
"topic": {"type": "string", "description": "对话主题(用于save操作)"},
"content": {"type": "string", "description": "详细内容(可选,用于save操作)"},
"keywords": {
"type": "array", "items": {"type": "string"},
"description": "搜索关键词列表"
},
"query": {"type": "string", "description": "搜索关键词(单个关键词)"},
"match_all": {"type": "boolean", "default": False},
"memory_id": {"type": "integer"},
"memory_ids": {"type": "array", "items": {"type": "integer"}},
"importance": {"type": "integer", "default": 2},
"limit": {"type": "integer", "default": 10, "description": "每页条数(默认10)"},
"page": {"type": "integer", "default": 1, "description": "页码(从1开始)"},
"confirm": {"type": "boolean", "default": False}
},
"required": ["action"]
}
def execute(self, action: str, **kwargs) -> str:
try:
if action == "save":
return self._save_memory(kwargs)
elif action == "search":
return self._search_memories(kwargs)
elif action == "recent":
return self._get_recent_memories(kwargs)
elif action == "get":
return self._get_memory_by_id(kwargs)
elif action == "delete":
return self._delete_memory(kwargs)
else:
return f"❌ 未知操作: {action}"
except Exception as e:
return f"❌ 记忆操作错误: {str(e)}"
def execute_streaming(self, output_queue: queue.Queue, **kwargs):
"""流式执行记忆操作"""
action = kwargs.get("action")
# 发送开始执行的消息
output_queue.put(("line", f"\n🧠 执行记忆操作: {action}\n"))
output_queue.put(("line", "-" * 50 + "\n"))
# 执行操作
result = self.execute(**kwargs)
# 发送结果
output_queue.put(("line", result + "\n"))
output_queue.put(("complete", result))
def _save_memory(self, kwargs: Dict) -> str:
summary = kwargs.get("summary")
if not summary:
return "❌ 需要提供总结内容"
topic = kwargs.get("topic", "未命名对话")
content = kwargs.get("content")
importance = kwargs.get("importance", constants.MEMORY_DEFAULT_IMPORTANCE)
memory_id = self.memory_db.add_memory(
topic=topic,
summary=summary,
content=content,
importance=importance
)
return self.format_result(True, "记忆已保存", {
"记忆ID": memory_id,
"主题": topic,
"总结": summary[:100] + "..." if len(summary) > 100 else summary
})
def _search_memories(self, kwargs: Dict) -> str:
"""搜索记忆 - 支持分页"""
keywords = kwargs.get("keywords", [])
query = kwargs.get("query", "")
match_all = kwargs.get("match_all", False)
page = kwargs.get("page", 1)
page_size = kwargs.get("limit", 10) # 使用 limit 作为 page_size
# 确保页码和每页条数有效
page = max(1, page)
page_size = max(1, min(50, page_size)) # 每页最多50条
# 处理关键词
if keywords and isinstance(keywords, list):
pass
elif query:
keywords = query.split()
else:
return "❌ 需要提供搜索关键词(keywords 或 query)"
if not keywords:
return "❌ 关键词列表为空"
# 先获取所有结果(不分页,用于计算总数)
all_results = self.memory_db.search_memories(keywords, match_all, limit=None)
if not all_results:
match_mode = "所有" if match_all else "任一"
return f"❌ 未找到包含 {match_mode} 关键词 {keywords} 的记忆"
# 计算分页信息
total_count = len(all_results)
total_pages = (total_count + page_size - 1) // page_size
# 检查页码是否有效
if page > total_pages and total_pages > 0:
return f"❌ 页码 {page} 超出范围,共有 {total_pages} 页"
# 计算当前页的起止索引
start_idx = (page - 1) * page_size
end_idx = min(start_idx + page_size, total_count)
# 获取当前页的结果
page_results = all_results[start_idx:end_idx]
# 构建输出
match_mode = "所有关键词" if match_all else "任一关键词"
output = [
f"🔍 搜索 {match_mode} {keywords} 找到 {total_count} 条记忆",
f"📄 第 {page}/{total_pages} 页 (每页 {page_size} 条)",
"-" * 40
]
for i, mem in enumerate(page_results, 1):
# 全局序号
global_idx = start_idx + i
output.append(f"\n{global_idx}. [ID: {mem['id']}] [{mem['end_time']}] {mem['topic']}")
output.append(f" 总结: {mem['summary'][:200]}")
if mem.get('tags'):
output.append(f" 标签: {', '.join(mem['tags'])}")
if mem.get('importance'):
output.append(f" 重要性: {'⭐' * mem['importance']} ({mem['importance']}/5)")
# 添加翻页提示
if total_pages > 1:
output.append(f"\n📖 翻页提示: 使用 page 参数查看更多结果")
output.append(f" 例如: memory search query='{query}' page={page + 1 if page < total_pages else page} limit={page_size}")
if page < total_pages:
output.append(f" ➡️ 下一页: page={page + 1}")
if page > 1:
output.append(f" ⬅️ 上一页: page={page - 1}")
return "\n".join(output)
def _get_recent_memories(self, kwargs: Dict) -> str:
"""获取最近的记忆 - 支持分页"""
page = kwargs.get("page", 1)
page_size = kwargs.get("limit", 10)
# 确保页码和每页条数有效
page = max(1, page)
page_size = max(1, min(50, page_size))
# 先获取所有结果
all_results = self.memory_db.get_recent_memories(limit=None)
if not all_results:
return "📭 没有找到记忆"
# 计算分页信息
total_count = len(all_results)
total_pages = (total_count + page_size - 1) // page_size
# 检查页码是否有效
if page > total_pages and total_pages > 0:
return f"❌ 页码 {page} 超出范围,共有 {total_pages} 页"
# 计算当前页的起止索引
start_idx = (page - 1) * page_size
end_idx = min(start_idx + page_size, total_count)
# 获取当前页的结果
page_results = all_results[start_idx:end_idx]
output = [f"📚 最近 {total_count} 条记忆 (第 {page}/{total_pages} 页,每页 {page_size} 条):"]
output.append("-" * 40)
for i, mem in enumerate(page_results, 1):
global_idx = start_idx + i
output.append(f"\n{global_idx}. [ID: {mem['id']}] [{mem['end_time']}] {mem['topic']}")
output.append(f" 总结: {mem['summary'][:100]}")
# 添加翻页提示
if total_pages > 1:
output.append(f"\n📖 翻页提示: 使用 page 参数查看更多结果")
output.append(f" 例如: memory recent page={page + 1 if page < total_pages else page} limit={page_size}")
return "\n".join(output)
def _get_memory_by_id(self, kwargs: Dict) -> str:
"""根据ID获取记忆"""
memory_id = kwargs.get("memory_id")
if not memory_id:
return "❌ 需要提供记忆ID"
memory = self.memory_db.get_memory_by_id(memory_id)
if not memory:
return f"❌ 未找到ID为 {memory_id} 的记忆"
output = [
f"📌 记忆 #{memory_id}",
f"主题: {memory['topic']}",
f"时间: {memory['start_time']} - {memory['end_time']}",
f"总结: {memory['summary']}",
]
if memory.get('content'):
output.append(f"详细内容:\n{memory['content']}")
if memory.get('tags'):
output.append(f"标签: {', '.join(memory['tags'])}")
if memory.get('importance'):
output.append(f"重要性: {'⭐' * memory['importance']} ({memory['importance']}/5)")
return "\n".join(output)
def _delete_memory(self, kwargs: Dict) -> str:
"""删除记忆"""
confirm = kwargs.get("confirm", False)
if not confirm:
return "❌ 需要设置 confirm=True 来确认删除操作"
memory_id = kwargs.get("memory_id")
memory_ids = kwargs.get("memory_ids", [])
deleted_count = 0
if memory_id:
if self.memory_db.delete_memory(memory_id):
deleted_count = 1
elif memory_ids:
deleted_count = self.memory_db.delete_memories_by_ids(memory_ids)
else:
return "❌ 需要提供 memory_id 或 memory_ids"
if deleted_count == 0:
return f"❌ 未找到要删除的记忆"
return self.format_result(True, f"成功删除 {deleted_count} 条记忆", {"删除数量": deleted_count})
# ==================== 备忘录工具 ====================
class MemoTool(Tool):
"""备忘录工具 - 用于管理任务(所有任务都是对话任务)"""
def __init__(self, memo_db: MemoDatabase, logger: Optional[Logger] = None):
super().__init__(logger)
self.memo_db = memo_db
def get_name(self) -> str:
return "memo"
def get_description(self) -> str:
return """任务管理工具 - 创建、查看、完成任务
支持:创建任务(支持一次性/重复、即时任务)、查看待办、标记完成、删除任务、搜索任务"""
def get_parameters(self) -> Dict:
return {
"type": "object",
"properties": {
"action": {
"type": "string",
"description": "操作类型",
"enum": ["add", "list", "get", "complete", "delete", "search", "stats"]
},
"title": {
"type": "string",
"description": "任务标题(用于add操作)"
},
"content": {
"type": "string",
"description": "任务内容(可选,用于add操作)"
},
"reminder_time": {
"type": "string",
"description": "执行时间(格式:YYYY-MM-DD HH:MM:SS,或相对时间如 '+1h', '+30m', 'tomorrow 9am')"
},
"reminder_minutes": {
"type": "integer",
"description": "延迟分钟数(从现在开始,用于快速设置任务)"
},
"tags": {
"type": "array",
"items": {"type": "string"},
"description": "标签列表"
},
"priority": {
"type": "integer",
"description": "优先级 1-5(默认1)",
"default": 1
},
"repeat_type": {
"type": "string",
"description": "重复类型(none=一次性, daily=每天, weekly=每周, monthly=每月, custom=自定义)",
"enum": ["none", "daily", "weekly", "monthly", "custom"],
"default": "none"
},
"repeat_interval": {
"type": "integer",
"description": "自定义重复间隔,当repeat_type='custom'时使用",
"default": 0
},
"repeat_interval_unit": {
"type": "string",
"description": "重复间隔单位(minutes=分钟, hours=小时, days=天),当repeat_type='custom'时使用",
"enum": ["minutes", "hours", "days"],
"default": "days"
},
"repeat_end_time": {
"type": "string",
"description": "重复结束时间,None表示永久重复"
},
"is_immediate": {
"type": "boolean",
"description": "是否为即时任务(立即执行)",
"default": False
},
"memo_id": {
"type": "integer",
"description": "任务ID(用于get、complete、delete操作)"
},
"memo_ids": {
"type": "array",
"items": {"type": "integer"},
"description": "任务ID列表(用于批量删除)"
},
"force_complete": {
"type": "boolean",
"description": "强制完成(忽略重复规则)",
"default": False
},
"keyword": {
"type": "string",
"description": "搜索关键词(用于search操作)"
},
"include_completed": {
"type": "boolean",
"description": "是否包含已完成的任务(默认False)",
"default": False
},
"limit": {
"type": "integer",
"description": "返回数量限制(默认20)",
"default": 20
},
"confirm": {
"type": "boolean",
"description": "确认删除操作",
"default": False
}
},
"required": ["action"]
}
def _parse_reminder_time(self, reminder_input: Any) -> Optional[str]:
"""解析提醒时间输入"""
if not reminder_input:
return None
if isinstance(reminder_input, int):
reminder_time = datetime.now() + timedelta(minutes=reminder_input)
return reminder_time.strftime(constants.DATETIME_FORMAT)
if isinstance(reminder_input, str):
reminder_input = reminder_input.strip()
if reminder_input.startswith('+'):
match = re.match(r'\+(\d+)([mhd])', reminder_input)
if match:
value, unit = int(match.group(1)), match.group(2)
if unit == 'm':
delta = timedelta(minutes=value)
elif unit == 'h':
delta = timedelta(hours=value)
elif unit == 'd':
delta = timedelta(days=value)
else:
return None
reminder_time = datetime.now() + delta
return reminder_time.strftime(constants.DATETIME_FORMAT)
try:
reminder_time = datetime.strptime(reminder_input, constants.DATETIME_FORMAT)
return reminder_time.strftime(constants.DATETIME_FORMAT)
except ValueError:
pass
now = datetime.now()
reminder_input_lower = reminder_input.lower()
if 'tomorrow' in reminder_input_lower:
tomorrow = now + timedelta(days=1)
time_match = re.search(r'(\d{1,2})(?::(\d{2}))?\s*(am|pm)?', reminder_input_lower)
if time_match:
hour = int(time_match.group(1))
minute = int(time_match.group(2)) if time_match.group(2) else 0
ampm = time_match.group(3)
if ampm:
if ampm == 'pm' and hour < 12:
hour += 12
elif ampm == 'am' and hour == 12:
hour = 0
reminder_time = tomorrow.replace(hour=hour, minute=minute, second=0)
else:
reminder_time = tomorrow.replace(hour=9, minute=0, second=0)
return reminder_time.strftime(constants.DATETIME_FORMAT)
return None
def execute(self, action: str, **kwargs) -> str:
"""执行备忘录操作"""
try:
if action == "add":
return self._add_memo(kwargs)
elif action == "list":
return self._list_memos(kwargs)
elif action == "get":
return self._get_memo(kwargs)
elif action == "complete":
return self._complete_memo(kwargs)
elif action == "delete":
return self._delete_memo(kwargs)
elif action == "search":
return self._search_memos(kwargs)
elif action == "stats":
return self._get_stats(kwargs)
else:
return f"❌ 未知操作: {action}"
except Exception as e:
return f"❌ 任务操作错误: {str(e)}"
def execute_streaming(self, output_queue: queue.Queue, **kwargs):
"""流式执行备忘录操作"""
action = kwargs.get("action")
# 发送开始执行的消息
output_queue.put(("line", f"\n📝 执行备忘录操作: {action}\n"))
output_queue.put(("line", "-" * 50 + "\n"))
# 执行操作
result = self.execute(**kwargs)
# 发送结果
output_queue.put(("line", result + "\n"))
output_queue.put(("complete", result))
def _add_memo(self, kwargs: Dict) -> str:
"""添加任务 - 所有任务都是对话任务,不需要task_type"""
title = kwargs.get("title")
if not title:
return "❌ 需要提供任务标题"
content = kwargs.get("content")
reminder_minutes = kwargs.get("reminder_minutes")
reminder_time_input = kwargs.get("reminder_time")
tags = kwargs.get("tags", [])
priority = kwargs.get("priority", 1)
repeat_type = kwargs.get("repeat_type", "none")
repeat_interval = kwargs.get("repeat_interval", 0)
repeat_interval_unit = kwargs.get("repeat_interval_unit", "days")
repeat_end_time = kwargs.get("repeat_end_time")
is_immediate = kwargs.get("is_immediate", False)
if repeat_type not in ["none", "daily", "weekly", "monthly", "custom"]:
return f"❌ 无效的重复类型: {repeat_type}"
if repeat_type == "custom" and repeat_interval <= 0:
return "❌ 自定义重复需要设置有效的 repeat_interval (>0)"
reminder_time = None
if is_immediate:
# 即时任务:设置为当前时间,确保立即执行
reminder_time = datetime.now().strftime(constants.DATETIME_FORMAT)
elif reminder_minutes:
reminder_time = self._parse_reminder_time(reminder_minutes)
elif reminder_time_input:
reminder_time = self._parse_reminder_time(reminder_time_input)
else:
# 默认30分钟后
reminder_time = self._parse_reminder_time(30)
memo_id = self.memo_db.add_memo(
title=title,
content=content,
reminder_time=reminder_time,
tags=tags,
priority=priority,
created_by="AI",
source="user",
repeat_type=repeat_type,
repeat_interval=repeat_interval,
repeat_interval_unit=repeat_interval_unit,
repeat_end_time=repeat_end_time,
is_immediate=is_immediate
)
data = {
"任务ID": memo_id,
"标题": title,
}
if is_immediate:
data["任务类型"] = "即时任务"
else:
if reminder_time:
data["执行时间"] = reminder_time
if repeat_type != "none":
if repeat_type == "custom":
repeat_info = f"重复类型: 每{repeat_interval} {repeat_interval_unit}"
else:
repeat_map = {
'daily': '每天',
'weekly': '每周',
'monthly': '每月'
}
repeat_info = f"重复类型: {repeat_map.get(repeat_type, repeat_type)}"
if repeat_end_time:
repeat_info += f", 结束于: {repeat_end_time}"
else:
repeat_info += ", 永久重复"
data["重复信息"] = repeat_info
return self.format_result(True, "任务已创建", data)
def _list_memos(self, kwargs: Dict) -> str:
"""列出任务"""
include_completed = kwargs.get("include_completed", False)
limit = kwargs.get("limit", 20)
memos = self.memo_db.get_all_memos(include_completed, limit)
if not memos:
return "📭 没有找到任务"
status = "所有" if include_completed else "待执行"
output = [f"📋 {status}任务 (共 {len(memos)} 个):"]
now = datetime.now()
for i, memo in enumerate(memos, 1):
status_icon = "✅" if memo['is_completed'] else "⏰"
priority_icon = "🔴" * memo['priority']
source_tag = f"[{memo.get('source', '未知')}]" if memo.get('source') else ""
immediate_tag = "⚡" if memo.get('is_immediate') else ""
repeat_tag = ""
if memo['repeat_type'] != 'none':
if memo['repeat_type'] == 'custom':
unit = memo.get('repeat_interval_unit', 'days')
unit_map = {'minutes': '分钟', 'hours': '小时', 'days': '天'}
repeat_tag = f" [每{memo['repeat_interval']}{unit_map.get(unit, unit)}]"
else:
repeat_map = {
'daily': ' [每天]',
'weekly': ' [每周]',
'monthly': ' [每月]'
}
repeat_tag = repeat_map.get(memo['repeat_type'], ' [重复]')
output.append(f"\n{i}. {status_icon}{immediate_tag} [ID: {memo['id']}] {source_tag}{repeat_tag} {memo['title']} {priority_icon}")
if memo['reminder_time']:
try:
reminder = datetime.strptime(memo['reminder_time'], constants.DATETIME_FORMAT)
if not memo['is_completed'] and reminder < now:
output.append(f" ⚠️ 已过期: {memo['reminder_time']}")
else:
output.append(f" ⏱️ 执行: {memo['reminder_time']}")
except:
output.append(f" ⏱️ 执行: {memo['reminder_time']}")
if memo.get('trigger_count', 0) > 0:
output.append(f" 🔄 已执行 {memo['trigger_count']} 次")
if memo['content']:
preview = memo['content'][:100] + "..." if len(memo['content']) > 100 else memo['content']
output.append(f" 📝 {preview}")
if memo['tags']:
output.append(f" 🏷️ {', '.join(memo['tags'])}")
return "\n".join(output)
def _get_memo(self, kwargs: Dict) -> str:
"""获取单个任务"""
memo_id = kwargs.get("memo_id")
if not memo_id:
return "❌ 需要提供任务ID"
memo = self.memo_db.get_memo(memo_id)
if not memo:
return f"❌ 未找到ID为 {memo_id} 的任务"
status = "✅ 已完成" if memo['is_completed'] else "⏰ 待执行"
if memo['is_completed']:
status += f" (完成于: {memo['completed_at']})"
immediate_tag = "⚡ 即时任务" if memo.get('is_immediate') else ""
output = [
f"📌 任务 #{memo_id} {immediate_tag}",
f"📋 状态: {status}",
f"📝 标题: {memo['title']}",
]
if memo.get('source'):
output.append(f"📎 来源: {memo['source']}")
if memo['repeat_type'] != 'none':
if memo['repeat_type'] == 'custom':
unit_map = {'minutes': '分钟', 'hours': '小时', 'days': '天'}
unit = memo.get('repeat_interval_unit', 'days')
repeat_info = f"🔄 重复: 每{memo['repeat_interval']} {unit_map.get(unit, unit)}"
else:
repeat_map = {
'daily': '每天',
'weekly': '每周',
'monthly': '每月'
}
repeat_info = f"🔄 重复: {repeat_map.get(memo['repeat_type'], memo['repeat_type'])}"
if memo['repeat_end_time']:
repeat_info += f", 结束于: {memo['repeat_end_time']}"
else:
repeat_info += ", 永久重复"
output.append(repeat_info)
if memo.get('trigger_count', 0) > 0:
output.append(f" 📊 已执行 {memo['trigger_count']} 次")
if memo.get('last_triggered_at'):
output.append(f" ⏱️ 上次执行: {memo['last_triggered_at']}")
if memo['content']:
output.append(f"📄 内容:\n{memo['content']}")
if memo['reminder_time']:
try:
now = datetime.now()
reminder = datetime.strptime(memo['reminder_time'], constants.DATETIME_FORMAT)
if not memo['is_completed'] and reminder < now:
output.append(f"⚠️ 执行时间 (已过期): {memo['reminder_time']}")
else:
output.append(f"⏰ 执行时间: {memo['reminder_time']}")
except:
output.append(f"⏰ 执行时间: {memo['reminder_time']}")
if memo['tags']:
output.append(f"🏷️ 标签: {', '.join(memo['tags'])}")
output.append(f"🔢 优先级: {'🔴' * memo['priority']} ({memo['priority']}/5)")
output.append(f"📅 创建时间: {memo['created_at']}")
output.append(f"👤 创建者: {memo['created_by'] or '系统'}")
return "\n".join(output)
def _complete_memo(self, kwargs: Dict) -> str:
"""完成任务"""
memo_id = kwargs.get("memo_id")
if not memo_id:
return "❌ 需要提供任务ID"
force_complete = kwargs.get("force_complete", False)
memo = self.memo_db.get_memo(memo_id)
if not memo:
return f"❌ 未找到ID为 {memo_id} 的任务"
if memo['is_completed'] and not force_complete:
return f"ℹ️ 任务 #{memo_id} 已经完成了"
if self.memo_db.complete_memo(memo_id, force_complete):
memo = self.memo_db.get_memo(memo_id)
data = {
"任务ID": memo_id,
"标题": memo['title']
}
if memo['repeat_type'] != 'none' and not memo['is_completed']:
data["下一次执行"] = memo['reminder_time']
data["已执行次数"] = memo.get('trigger_count', 0)
return self.format_result(True, "任务已处理,已更新下一次执行时间", data)
else:
return self.format_result(True, "任务已完成", data)
else:
return f"❌ 无法完成任务 #{memo_id}"
def _delete_memo(self, kwargs: Dict) -> str:
"""删除任务"""
confirm = kwargs.get("confirm", False)
if not confirm:
return "❌ 需要设置 confirm=True 来确认删除操作"
memo_id = kwargs.get("memo_id")
if memo_id:
if self.memo_db.complete_memo(memo_id, force_complete=True):
return self.format_result(True, f"任务 #{memo_id} 已删除")
else:
return f"❌ 无法删除任务 #{memo_id}"
else:
return "❌ 需要提供 memo_id"
def _search_memos(self, kwargs: Dict) -> str:
"""搜索任务"""
keyword = kwargs.get("keyword")
if not keyword:
return "❌ 需要提供搜索关键词"
include_completed = kwargs.get("include_completed", False)
limit = kwargs.get("limit", 20)
all_memos = self.memo_db.get_all_memos(include_completed, limit=100)
results = []
keyword_lower = keyword.lower()
for memo in all_memos:
if (keyword_lower in memo['title'].lower() or
(memo['content'] and keyword_lower in memo['content'].lower())):
results.append(memo)
if not results:
return f"❌ 未找到包含 '{keyword}' 的任务"
output = [f"🔍 搜索 '{keyword}' 找到 {len(results)} 个任务:"]
now = datetime.now()
for i, memo in enumerate(results[:limit], 1):
status_icon = "✅" if memo['is_completed'] else "⏰"
immediate_tag = "⚡" if memo.get('is_immediate') else ""
output.append(f"\n{i}. {status_icon}{immediate_tag} [ID: {memo['id']}] {memo['title']}")
if memo['reminder_time']:
try:
reminder = datetime.strptime(memo['reminder_time'], constants.DATETIME_FORMAT)
if not memo['is_completed'] and reminder < now:
output.append(f" ⚠️ 已过期: {memo['reminder_time']}")
else:
output.append(f" ⏱️ {memo['reminder_time']}")
except:
output.append(f" ⏱️ {memo['reminder_time']}")
return "\n".join(output)
def _get_stats(self, kwargs: Dict) -> str:
"""获取任务统计信息"""
stats = self.memo_db.get_memo_stats()
output = [
"📊 任务统计",
f" 总任务: {stats['total']} 个",
f" 待执行: {stats['pending']} 个",
f" 已过期: {stats['overdue']} 个",
f" 已完成: {stats['completed']} 个",
f" 即时任务: {stats['immediate']} 个"
]
if stats['overdue'] > 0:
output.append(f"\n⚠️ 有 {stats['overdue']} 个任务已过期,将自动执行")
return "\n".join(output)