fix: cache file tree per conversation
This commit is contained in:
parent
453df30f45
commit
5fcda980fb
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user