fix: persist search results and speed init

This commit is contained in:
JOJO 2026-01-02 12:14:04 +08:00
parent 9ae43e89ff
commit 95285747c0
2 changed files with 93 additions and 49 deletions

View File

@ -301,17 +301,18 @@ const appOptions = {
console.warn('CSRF token 初始化失败:', err);
});
}
await this.bootstrapRoute();
await this.initSocket();
// 并行启动路由解析与实时连接,避免等待路由加载阻塞思考模式同步
const routePromise = this.bootstrapRoute();
const socketPromise = this.initSocket();
await routePromise;
await socketPromise;
this.$nextTick(() => {
this.ensureScrollListener();
});
setupShowImageObserver();
// 延迟加载初始数据
setTimeout(() => {
this.loadInitialData();
}, 500);
// 立即加载初始数据(并行获取状态,优先同步运行模式)
this.loadInitialData();
document.addEventListener('click', this.handleClickOutsideQuickMenu);
document.addEventListener('click', this.handleClickOutsidePanelMenu);
@ -1288,54 +1289,56 @@ const appOptions = {
async loadInitialData() {
try {
debugLog('加载初始数据...');
await this.fileFetchTree();
await this.focusFetchFiles();
await this.fileFetchTodoList();
// 并行拉取文件/待办,优先获取状态以尽快同步运行模式
const treePromise = this.fileFetchTree();
const focusPromise = this.focusFetchFiles();
const todoPromise = this.fileFetchTodoList();
const statusResponse = await fetch('/api/status');
const statusData = await statusResponse.json();
this.projectPath = statusData.project_path || '';
this.agentVersion = statusData.version || this.agentVersion;
this.thinkingMode = !!statusData.thinking_mode;
this.applyStatusSnapshot(statusData);
await this.fetchUsageQuota();
// 立即更新配额和运行模式,避免等待其他慢接口
this.fetchUsageQuota();
// 获取当前对话信息
const statusConversationId = statusData.conversation && statusData.conversation.current_id;
if (statusConversationId) {
if (!this.currentConversationId) {
this.skipConversationHistoryReload = true;
// 首次从状态恢复对话时,避免 socket 的 conversation_loaded 再次触发历史加载
this.skipConversationLoadedEvent = true;
this.currentConversationId = statusConversationId;
if (statusConversationId && !this.currentConversationId) {
this.skipConversationHistoryReload = true;
// 首次从状态恢复对话时,避免 socket 的 conversation_loaded 再次触发历史加载
this.skipConversationLoadedEvent = true;
this.currentConversationId = statusConversationId;
// 如果有当前对话,尝试获取标题和历史
try {
const convResponse = await fetch(`/api/conversations/current`);
const convData = await convResponse.json();
if (convData.success && convData.data) {
this.currentConversationTitle = convData.data.title;
}
// 初始化时调用一次,因为 skipConversationHistoryReload 会阻止 watch 触发
if (
this.lastHistoryLoadedConversationId !== this.currentConversationId ||
!Array.isArray(this.messages) ||
this.messages.length === 0
) {
await this.fetchAndDisplayHistory();
}
// 获取当前对话的Token统计
this.fetchConversationTokenStatistics();
this.updateCurrentContextTokens();
} catch (e) {
console.warn('获取当前对话标题失败:', e);
// 如果有当前对话,尝试获取标题和历史
try {
const convResponse = await fetch(`/api/conversations/current`);
const convData = await convResponse.json();
if (convData.success && convData.data) {
this.currentConversationTitle = convData.data.title;
}
// 初始化时调用一次,因为 skipConversationHistoryReload 会阻止 watch 触发
if (
this.lastHistoryLoadedConversationId !== this.currentConversationId ||
!Array.isArray(this.messages) ||
this.messages.length === 0
) {
await this.fetchAndDisplayHistory();
}
// 获取当前对话的Token统计
this.fetchConversationTokenStatistics();
this.updateCurrentContextTokens();
} catch (e) {
console.warn('获取当前对话标题失败:', e);
}
}
// 等待其他加载项完成(允许部分失败不阻塞模式切换)
await Promise.allSettled([treePromise, focusPromise, todoPromise]);
await this.loadToolSettings(true);
debugLog('初始数据加载完成');
} catch (error) {
console.error('加载初始数据失败:', error);
@ -1835,17 +1838,19 @@ const appOptions = {
}
if (toolAction) {
// 解析工具结果
// 解析工具结果优先使用JSON其次使用元数据的 tool_payload以保证搜索结果在刷新后仍可展示
let result;
try {
// 尝试解析为JSON
result = JSON.parse(message.content);
} catch (e) {
// 如果不是JSON就作为纯文本
result = {
output: message.content,
success: true
};
if (message.metadata && message.metadata.tool_payload) {
result = message.metadata.tool_payload;
} else {
result = {
output: message.content,
success: true
};
}
}
toolAction.tool.status = 'completed';

View File

@ -545,6 +545,38 @@ def format_tool_result_notice(tool_name: str, tool_call_id: Optional[str], conte
body = "(无附加输出)"
return f"{header}\n{body}"
def compact_web_search_result(result_data: Dict[str, Any]) -> Dict[str, Any]:
"""提取 web_search 结果中前端展示所需的关键字段,避免持久化时丢失列表。"""
if not isinstance(result_data, dict):
return {"success": False, "error": "invalid search result"}
compact: Dict[str, Any] = {
"success": bool(result_data.get("success")),
"summary": result_data.get("summary"),
"query": result_data.get("query"),
"filters": result_data.get("filters") or {},
"total_results": result_data.get("total_results", 0)
}
# 仅保留前端需要渲染的字段,避免巨大正文导致历史加载时缺失
items: List[Dict[str, Any]] = []
for item in result_data.get("results") or []:
if not isinstance(item, dict):
continue
items.append({
"index": item.get("index"),
"title": item.get("title") or item.get("name"),
"url": item.get("url")
})
compact["results"] = items
if not compact.get("success") and result_data.get("error"):
compact["error"] = result_data.get("error")
return compact
# 创建调试日志文件
DEBUG_LOG_FILE = Path(LOGS_DIR).expanduser().resolve() / "debug_stream.log"
CHUNK_BACKEND_LOG_FILE = Path(LOGS_DIR).expanduser().resolve() / "chunk_backend.log"
@ -4751,7 +4783,14 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
# ===== 增量保存:立即保存工具结果 =====
metadata_payload = None
if isinstance(result_data, dict):
tool_result_content = format_tool_result_for_context(function_name, result_data, tool_result)
# 特殊处理 web_search保留可供前端渲染的精简结构以便历史记录复现搜索结果
if function_name == "web_search":
try:
tool_result_content = json.dumps(compact_web_search_result(result_data), ensure_ascii=False)
except Exception:
tool_result_content = tool_result
else:
tool_result_content = format_tool_result_for_context(function_name, result_data, tool_result)
metadata_payload = {"tool_payload": result_data}
else:
tool_result_content = tool_result