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.todo_list: Optional[Dict[str, Any]] = None
|
||||||
self.has_images: bool = False
|
self.has_images: bool = False
|
||||||
self.image_compression_mode: str = "original"
|
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)
|
self.conversation_manager = ConversationManager(base_dir=self.data_dir)
|
||||||
@ -329,6 +332,8 @@ class ContextManager:
|
|||||||
self.conversation_history = []
|
self.conversation_history = []
|
||||||
self.todo_list = None
|
self.todo_list = None
|
||||||
self.has_images = False
|
self.has_images = False
|
||||||
|
self.conversation_metadata = {}
|
||||||
|
self.project_snapshot = None
|
||||||
|
|
||||||
print(f"📝 开始新对话: {conversation_id}")
|
print(f"📝 开始新对话: {conversation_id}")
|
||||||
return conversation_id
|
return conversation_id
|
||||||
@ -358,6 +363,17 @@ class ContextManager:
|
|||||||
self.conversation_history = conversation_data.get("messages", [])
|
self.conversation_history = conversation_data.get("messages", [])
|
||||||
todo_data = conversation_data.get("todo_list")
|
todo_data = conversation_data.get("todo_list")
|
||||||
self.todo_list = deepcopy(todo_data) if todo_data else None
|
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", {})
|
metadata = conversation_data.get("metadata", {})
|
||||||
@ -400,6 +416,49 @@ class ContextManager:
|
|||||||
|
|
||||||
return True
|
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:
|
def save_current_conversation(self) -> bool:
|
||||||
"""
|
"""
|
||||||
保存当前对话
|
保存当前对话
|
||||||
@ -954,16 +1013,19 @@ class ContextManager:
|
|||||||
|
|
||||||
def build_main_context(self, memory_content: str) -> Dict:
|
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 = {
|
context = {
|
||||||
"project_info": {
|
"project_info": {
|
||||||
"path": str(self.project_path),
|
"path": str(self.project_path),
|
||||||
"file_tree": self._build_file_tree(structure),
|
"file_tree": snapshot.get("file_tree", ""),
|
||||||
"file_annotations": self.file_annotations,
|
"file_annotations": self.file_annotations,
|
||||||
"statistics": {
|
"statistics": {
|
||||||
"total_files": structure["total_files"],
|
"total_files": total_files,
|
||||||
"total_size": f"{structure['total_size'] / 1024 / 1024:.2f}MB"
|
"total_size": f"{total_size_bytes / 1024 / 1024:.2f}MB"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"memory": memory_content,
|
"memory": memory_content,
|
||||||
@ -981,14 +1043,21 @@ class ContextManager:
|
|||||||
execution_results: List[Dict] = None
|
execution_results: List[Dict] = None
|
||||||
) -> Dict:
|
) -> 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 = {
|
context = {
|
||||||
"task_info": task_info,
|
"task_info": task_info,
|
||||||
"project_info": {
|
"project_info": {
|
||||||
"path": str(self.project_path),
|
"path": str(self.project_path),
|
||||||
"file_tree": self._build_file_tree(structure),
|
"file_tree": snapshot.get("file_tree", ""),
|
||||||
"file_annotations": self.file_annotations
|
"file_annotations": self.file_annotations,
|
||||||
|
"statistics": {
|
||||||
|
"total_files": total_files,
|
||||||
|
"total_size": f"{total_size_bytes / 1024 / 1024:.2f}MB"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"main_memory": main_memory,
|
"main_memory": main_memory,
|
||||||
|
|||||||
@ -347,6 +347,10 @@ class ConversationManager:
|
|||||||
"run_mode": normalized_mode,
|
"run_mode": normalized_mode,
|
||||||
"model_key": model_key,
|
"model_key": model_key,
|
||||||
"has_images": has_images,
|
"has_images": has_images,
|
||||||
|
# 首次对话尚未生成文件树快照,待首次用户消息时填充
|
||||||
|
"project_file_tree": None,
|
||||||
|
"project_statistics": None,
|
||||||
|
"project_snapshot_at": None,
|
||||||
"total_messages": len(messages),
|
"total_messages": len(messages),
|
||||||
"total_tools": self._count_tools_in_messages(messages),
|
"total_tools": self._count_tools_in_messages(messages),
|
||||||
"status": "active"
|
"status": "active"
|
||||||
@ -454,7 +458,10 @@ class ConversationManager:
|
|||||||
run_mode: Optional[str] = None,
|
run_mode: Optional[str] = None,
|
||||||
todo_list: Optional[Dict] = None,
|
todo_list: Optional[Dict] = None,
|
||||||
model_key: Optional[str] = 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:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
保存对话(更新现有对话)
|
保存对话(更新现有对话)
|
||||||
@ -522,6 +529,19 @@ class ConversationManager:
|
|||||||
existing_data["metadata"]["has_images"] = bool(has_images)
|
existing_data["metadata"]["has_images"] = bool(has_images)
|
||||||
elif "has_images" not in existing_data["metadata"]:
|
elif "has_images" not in existing_data["metadata"]:
|
||||||
existing_data["metadata"]["has_images"] = False
|
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_messages"] = len(messages)
|
||||||
existing_data["metadata"]["total_tools"] = self._count_tools_in_messages(messages)
|
existing_data["metadata"]["total_tools"] = self._count_tools_in_messages(messages)
|
||||||
@ -546,6 +566,33 @@ class ConversationManager:
|
|||||||
print(f"⌘ 保存对话失败 {conversation_id}: {e}")
|
print(f"⌘ 保存对话失败 {conversation_id}: {e}")
|
||||||
return False
|
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]:
|
def load_conversation(self, conversation_id: str) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
加载对话数据
|
加载对话数据
|
||||||
@ -589,6 +636,22 @@ class ConversationManager:
|
|||||||
self._save_conversation_file(conversation_id, data)
|
self._save_conversation_file(conversation_id, data)
|
||||||
print(f"🔧 为对话 {conversation_id} 添加运行模式字段")
|
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:
|
if metadata.get("model_key") is None:
|
||||||
inferred_model = None
|
inferred_model = None
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user