fix: cache file tree per conversation

This commit is contained in:
JOJO 2026-01-29 15:05:29 +08:00
parent 453df30f45
commit 5fcda980fb
2 changed files with 140 additions and 8 deletions

View File

@ -54,6 +54,9 @@ class ContextManager:
self.todo_list: Optional[Dict[str, Any]] = None
self.has_images: bool = False
self.image_compression_mode: str = "original"
# 对话元数据与项目快照缓存
self.conversation_metadata: Dict[str, Any] = {}
self.project_snapshot: Optional[Dict[str, Any]] = None
# 新增:对话持久化管理器
self.conversation_manager = ConversationManager(base_dir=self.data_dir)
@ -329,6 +332,8 @@ class ContextManager:
self.conversation_history = []
self.todo_list = None
self.has_images = False
self.conversation_metadata = {}
self.project_snapshot = None
print(f"📝 开始新对话: {conversation_id}")
return conversation_id
@ -358,6 +363,17 @@ class ContextManager:
self.conversation_history = conversation_data.get("messages", [])
todo_data = conversation_data.get("todo_list")
self.todo_list = deepcopy(todo_data) if todo_data else None
self.conversation_metadata = deepcopy(conversation_data.get("metadata", {}) or {})
# 恢复项目文件树快照(如已存在)
meta = self.conversation_metadata
if meta.get("project_file_tree"):
self.project_snapshot = {
"file_tree": meta.get("project_file_tree"),
"statistics": meta.get("project_statistics"),
"snapshot_at": meta.get("project_snapshot_at")
}
else:
self.project_snapshot = None
# 更新项目路径(如果对话中有的话)
metadata = conversation_data.get("metadata", {})
@ -400,6 +416,49 @@ class ContextManager:
return True
def _ensure_project_snapshot(self) -> Dict[str, Any]:
"""
确保当前对话拥有项目文件树快照
- 若已缓存/存档则直接返回
- 若不存在则第一次扫描目录并存入对话文件后续复用
"""
if self.project_snapshot:
return self.project_snapshot
meta = self.conversation_metadata or {}
stored_tree = meta.get("project_file_tree")
if stored_tree:
self.project_snapshot = {
"file_tree": stored_tree,
"statistics": meta.get("project_statistics"),
"snapshot_at": meta.get("project_snapshot_at")
}
return self.project_snapshot
# 首次生成并缓存
structure = self.get_project_structure()
snapshot = {
"file_tree": self._build_file_tree(structure),
"statistics": {
"total_files": structure["total_files"],
"total_size": structure["total_size"]
},
"snapshot_at": datetime.now().isoformat()
}
self.project_snapshot = snapshot
if self.current_conversation_id:
self.conversation_manager.update_project_snapshot(
self.current_conversation_id,
project_file_tree=snapshot["file_tree"],
project_statistics=snapshot["statistics"],
project_snapshot_at=snapshot["snapshot_at"]
)
# 同步内存元数据
self.conversation_metadata["project_file_tree"] = snapshot["file_tree"]
self.conversation_metadata["project_statistics"] = snapshot["statistics"]
self.conversation_metadata["project_snapshot_at"] = snapshot["snapshot_at"]
return snapshot
def save_current_conversation(self) -> bool:
"""
保存当前对话
@ -954,16 +1013,19 @@ class ContextManager:
def build_main_context(self, memory_content: str) -> Dict:
"""构建主终端上下文"""
structure = self.get_project_structure()
snapshot = self._ensure_project_snapshot()
stats = snapshot.get("statistics") or {}
total_files = stats.get("total_files", 0)
total_size_bytes = stats.get("total_size", 0)
context = {
"project_info": {
"path": str(self.project_path),
"file_tree": self._build_file_tree(structure),
"file_tree": snapshot.get("file_tree", ""),
"file_annotations": self.file_annotations,
"statistics": {
"total_files": structure["total_files"],
"total_size": f"{structure['total_size'] / 1024 / 1024:.2f}MB"
"total_files": total_files,
"total_size": f"{total_size_bytes / 1024 / 1024:.2f}MB"
}
},
"memory": memory_content,
@ -981,14 +1043,21 @@ class ContextManager:
execution_results: List[Dict] = None
) -> Dict:
"""构建子任务上下文"""
structure = self.get_project_structure()
snapshot = self._ensure_project_snapshot()
stats = snapshot.get("statistics") or {}
total_files = stats.get("total_files", 0)
total_size_bytes = stats.get("total_size", 0)
context = {
"task_info": task_info,
"project_info": {
"path": str(self.project_path),
"file_tree": self._build_file_tree(structure),
"file_annotations": self.file_annotations
"file_tree": snapshot.get("file_tree", ""),
"file_annotations": self.file_annotations,
"statistics": {
"total_files": total_files,
"total_size": f"{total_size_bytes / 1024 / 1024:.2f}MB"
}
},
"memory": {
"main_memory": main_memory,

View File

@ -347,6 +347,10 @@ class ConversationManager:
"run_mode": normalized_mode,
"model_key": model_key,
"has_images": has_images,
# 首次对话尚未生成文件树快照,待首次用户消息时填充
"project_file_tree": None,
"project_statistics": None,
"project_snapshot_at": None,
"total_messages": len(messages),
"total_tools": self._count_tools_in_messages(messages),
"status": "active"
@ -454,7 +458,10 @@ class ConversationManager:
run_mode: Optional[str] = None,
todo_list: Optional[Dict] = None,
model_key: Optional[str] = None,
has_images: Optional[bool] = None
has_images: Optional[bool] = None,
project_file_tree: Optional[str] = None,
project_statistics: Optional[Dict] = None,
project_snapshot_at: Optional[str] = None
) -> bool:
"""
保存对话更新现有对话
@ -522,6 +529,19 @@ class ConversationManager:
existing_data["metadata"]["has_images"] = bool(has_images)
elif "has_images" not in existing_data["metadata"]:
existing_data["metadata"]["has_images"] = False
# 文件树快照(如果有新值则更新,若已有则保持)
if project_file_tree is not None:
existing_data["metadata"]["project_file_tree"] = project_file_tree
elif "project_file_tree" not in existing_data["metadata"]:
existing_data["metadata"]["project_file_tree"] = None
if project_statistics is not None:
existing_data["metadata"]["project_statistics"] = project_statistics
elif "project_statistics" not in existing_data["metadata"]:
existing_data["metadata"]["project_statistics"] = None
if project_snapshot_at is not None:
existing_data["metadata"]["project_snapshot_at"] = project_snapshot_at
elif "project_snapshot_at" not in existing_data["metadata"]:
existing_data["metadata"]["project_snapshot_at"] = None
existing_data["metadata"]["total_messages"] = len(messages)
existing_data["metadata"]["total_tools"] = self._count_tools_in_messages(messages)
@ -546,6 +566,33 @@ class ConversationManager:
print(f"⌘ 保存对话失败 {conversation_id}: {e}")
return False
def update_project_snapshot(
self,
conversation_id: str,
project_file_tree: str,
project_statistics: Optional[Dict],
project_snapshot_at: Optional[str] = None
) -> bool:
"""
单独更新对话的项目文件树快照不修改消息内容
便于在首次用户消息时写入固定文件结构
"""
try:
data = self.load_conversation(conversation_id)
if not data:
return False
meta = data.get("metadata", {}) or {}
meta["project_file_tree"] = project_file_tree
meta["project_statistics"] = project_statistics
meta["project_snapshot_at"] = project_snapshot_at
data["metadata"] = meta
self._save_conversation_file(conversation_id, data)
self._update_index(conversation_id, data)
return True
except Exception as exc:
print(f"⌘ 更新项目快照失败 {conversation_id}: {exc}")
return False
def load_conversation(self, conversation_id: str) -> Optional[Dict]:
"""
加载对话数据
@ -589,6 +636,22 @@ class ConversationManager:
self._save_conversation_file(conversation_id, data)
print(f"🔧 为对话 {conversation_id} 添加运行模式字段")
# 确保项目快照字段存在(向后兼容)
changed = False
if "project_file_tree" not in metadata:
metadata["project_file_tree"] = None
changed = True
if "project_statistics" not in metadata:
metadata["project_statistics"] = None
changed = True
if "project_snapshot_at" not in metadata:
metadata["project_snapshot_at"] = None
changed = True
if changed:
data["metadata"] = metadata
self._save_conversation_file(conversation_id, data)
print(f"🔧 为对话 {conversation_id} 补齐项目快照字段")
# 回填缺失的模型字段:从最近的助手消息元数据推断
if metadata.get("model_key") is None:
inferred_model = None