feat(static): 进入对话统一走 bootstrap 协议(方案B)
新增 enterConversation 单一入口:一次 GET bootstrap 拿全量数据, restoreTaskState 增加快速路径(注入 task/events 跳过三次串行请求与 4s 死等,消除运行中刷新的两段式体验);needsRebuild 仍前端重算保证 恢复行为与现状等价。PUT load 保留后端兼容、前端不再调用。
This commit is contained in:
parent
21f3acdf16
commit
858ff45187
134
static/src/app/methods/conversation/bootstrap.ts
Normal file
134
static/src/app/methods/conversation/bootstrap.ts
Normal file
@ -0,0 +1,134 @@
|
||||
// @ts-nocheck
|
||||
import { debugLog, traceLog } from '../common';
|
||||
|
||||
/**
|
||||
* 统一加载协议(方案 B):进入对话的单一入口。
|
||||
*
|
||||
* 一次 GET bootstrap 拿「元数据 + 文件态历史 + 运行状态 + 任务重放决策」,
|
||||
* 替代旧的「PUT load + GET messages + GET tasks + 历史死等重试」串行链。
|
||||
* 后端接口纯只读,不切换任何 terminal 上下文,天然规避 safe_nav 双轨问题。
|
||||
*
|
||||
* 设计文档:docs/conversation_load_unification_plan.md
|
||||
*/
|
||||
export const bootstrapMethods = {
|
||||
/**
|
||||
* @param conversationId 对话 ID(可带或不带 conv_ 前缀)
|
||||
* @param options.source 'refresh' | 'sidebar'(仅用于日志追踪)
|
||||
* @param options.workspaceId 目标工作区(host/docker 多工作区场景)
|
||||
* @param options.urlMode 'push' | 'replace' | 'none'(缺省 push)
|
||||
* @param options.urlPrefix 显式 URL 前缀(缺省按 multiAgentMode 自适应)
|
||||
* @param options.preserveListPosition 保持侧边栏列表位置不置顶
|
||||
* @param options.resetUI 渲染前 resetAllStates(sidebar 切换保留「清空→填入」语义)
|
||||
*/
|
||||
async enterConversation(conversationId, options = {}) {
|
||||
const {
|
||||
source = 'sidebar',
|
||||
workspaceId = '',
|
||||
urlMode = 'push',
|
||||
urlPrefix = '',
|
||||
preserveListPosition = false,
|
||||
resetUI = false
|
||||
} = options;
|
||||
|
||||
const isHostLikeMode = Boolean(this.versioningHostMode || this.dockerProjectMode);
|
||||
const requestUrl = workspaceId && isHostLikeMode
|
||||
? `/api/conversations/${conversationId}/bootstrap?workspace_id=${encodeURIComponent(workspaceId)}`
|
||||
: `/api/conversations/${conversationId}/bootstrap`;
|
||||
|
||||
traceLog('enterConversation:start', { conversationId, source, urlMode });
|
||||
const response = await fetch(requestUrl);
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
debugLog('enterConversation:failed', { conversationId, error: result.error || result.message });
|
||||
return result;
|
||||
}
|
||||
|
||||
const data = result.data || {};
|
||||
const meta = data.meta || {};
|
||||
const normalizedId = data.conversation_id || conversationId;
|
||||
|
||||
// 1. 应用模式/模型(与旧 PUT load 响应处理对齐)
|
||||
if (typeof meta.run_mode === 'string') {
|
||||
this.runMode = meta.run_mode;
|
||||
this.thinkingMode =
|
||||
typeof meta.thinking_mode === 'boolean' ? meta.thinking_mode : meta.run_mode !== 'fast';
|
||||
} else if (typeof meta.thinking_mode === 'boolean') {
|
||||
this.thinkingMode = meta.thinking_mode;
|
||||
this.runMode = meta.thinking_mode ? 'thinking' : 'fast';
|
||||
}
|
||||
if (typeof meta.model_key === 'string' && meta.model_key) {
|
||||
this.modelSet(meta.model_key);
|
||||
}
|
||||
if (typeof meta.multi_agent_mode === 'boolean') {
|
||||
this.multiAgentMode = meta.multi_agent_mode;
|
||||
}
|
||||
|
||||
// 2. 当前对话状态(skip 标记阻止 currentConversationId watch 重复拉历史)
|
||||
this.skipConversationHistoryReload = true;
|
||||
this.currentConversationId = normalizedId;
|
||||
this.refreshProjectGitSummary?.();
|
||||
this.fetchTerminalCount();
|
||||
if (!preserveListPosition) {
|
||||
this.promoteConversationToTop(normalizedId);
|
||||
}
|
||||
if (urlMode !== 'none') {
|
||||
const prefix = urlPrefix || (this.multiAgentMode ? '/multiagent/' : '/');
|
||||
const stateMethod = urlMode === 'replace' ? 'replaceState' : 'pushState';
|
||||
history[stateMethod](
|
||||
{ conversationId: normalizedId },
|
||||
'',
|
||||
`${prefix}${this.stripConversationPrefix(normalizedId)}`
|
||||
);
|
||||
}
|
||||
this.skipConversationLoadedEvent = true;
|
||||
|
||||
// 3. 重置 UI(sidebar 切换保留「清空→填入」语义;刷新路径不传以避免闪烁)
|
||||
if (resetUI) {
|
||||
this.resetAllStates(`enterConversation:${normalizedId}`);
|
||||
}
|
||||
|
||||
// 4. 渲染文件态历史(复用现有渲染器;设置防重标记避免 loadInitialData 二次拉取)
|
||||
const messages = Array.isArray(data.messages) ? data.messages : [];
|
||||
this.logMessageState?.('enterConversation:before-render', {
|
||||
conversationId: normalizedId,
|
||||
count: messages.length
|
||||
});
|
||||
this.messages = [];
|
||||
if (messages.length > 0) {
|
||||
this.renderHistoryMessages(messages);
|
||||
// 与 fetchAndDisplayHistory 一致:隐藏期内等待动态高度稳定再滚到底
|
||||
if (typeof this.settleHistoryRenderAndScroll === 'function') {
|
||||
await this.settleHistoryRenderAndScroll();
|
||||
} else {
|
||||
await this.$nextTick();
|
||||
this.scrollHistoryToBottomInstant();
|
||||
}
|
||||
}
|
||||
this.lastHistoryLoadedConversationId = normalizedId;
|
||||
this.refreshBlankHeroState();
|
||||
|
||||
// 5. 运行中任务快速恢复(任务/事件/判据已由 bootstrap 聚合,
|
||||
// 免去 GET /api/tasks + 历史死等 + GET /api/tasks/{id} 三次请求)
|
||||
const running = data.running || {};
|
||||
if (running.is_main_running && data.task_replay) {
|
||||
await this.restoreTaskState({ bootstrapReplay: data.task_replay });
|
||||
}
|
||||
|
||||
traceLog('enterConversation:done', {
|
||||
conversationId: normalizedId,
|
||||
messagesCount: messages.length,
|
||||
isTrulyActive: !!running.is_truly_active,
|
||||
needsRebuild: data.task_replay?.needs_rebuild
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
title: meta.title || '',
|
||||
run_mode: meta.run_mode,
|
||||
thinking_mode: meta.thinking_mode,
|
||||
model_key: meta.model_key,
|
||||
multi_agent_mode: meta.multi_agent_mode,
|
||||
conversation_id: normalizedId
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -2,9 +2,11 @@
|
||||
import { stateMethods } from './state';
|
||||
import { loadMethods } from './load';
|
||||
import { actionMethods } from './action';
|
||||
import { bootstrapMethods } from './bootstrap';
|
||||
|
||||
export const conversationMethods = {
|
||||
...stateMethods,
|
||||
...loadMethods,
|
||||
...actionMethods,
|
||||
...bootstrapMethods,
|
||||
};
|
||||
|
||||
@ -79,7 +79,6 @@ export const loadMethods = {
|
||||
const force = Boolean(options.force);
|
||||
const preserveListPosition = Boolean(options.preserveListPosition);
|
||||
const workspaceId = options.workspaceId ? String(options.workspaceId) : '';
|
||||
const isHostLikeMode = Boolean(this.versioningHostMode || this.dockerProjectMode);
|
||||
debugLog('加载对话:', conversationId);
|
||||
traceLog('loadConversation:start', {
|
||||
conversationId,
|
||||
@ -155,75 +154,35 @@ export const loadMethods = {
|
||||
}).catch(() => {});
|
||||
|
||||
try {
|
||||
// 1. 调用加载API(多工作区模式下携带目标 workspace_id,避免并发/session 漂移导致加载错误工作区)
|
||||
const loadUrl = workspaceId && isHostLikeMode
|
||||
? `/api/conversations/${conversationId}/load?workspace_id=${encodeURIComponent(workspaceId)}`
|
||||
: `/api/conversations/${conversationId}/load`;
|
||||
const response = await fetch(loadUrl, {
|
||||
method: 'PUT'
|
||||
// 统一加载协议:一次 bootstrap 替代「PUT load + GET messages + GET tasks」串行链。
|
||||
// 模式/模型应用、历史渲染、运行中任务恢复均在 enterConversation 内完成。
|
||||
const result = await this.enterConversation(conversationId, {
|
||||
source: 'sidebar',
|
||||
workspaceId,
|
||||
urlMode: 'push',
|
||||
preserveListPosition,
|
||||
resetUI: true
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
debugLog('对话加载API成功:', result);
|
||||
debugLog('对话 bootstrap 成功:', result);
|
||||
traceLog('loadConversation:api-success', { conversationId, title: result.title });
|
||||
if (typeof result.run_mode === 'string') {
|
||||
this.runMode = result.run_mode;
|
||||
this.thinkingMode =
|
||||
typeof result.thinking_mode === 'boolean'
|
||||
? result.thinking_mode
|
||||
: result.run_mode !== 'fast';
|
||||
} else if (typeof result.thinking_mode === 'boolean') {
|
||||
this.thinkingMode = result.thinking_mode;
|
||||
this.runMode = result.thinking_mode ? 'thinking' : 'fast';
|
||||
}
|
||||
if (typeof result.model_key === 'string' && result.model_key) {
|
||||
this.modelSet(result.model_key);
|
||||
}
|
||||
if (typeof result.multi_agent_mode === 'boolean') {
|
||||
this.multiAgentMode = result.multi_agent_mode;
|
||||
}
|
||||
|
||||
// 2. 更新当前对话信息
|
||||
this.skipConversationHistoryReload = true;
|
||||
this.currentConversationId = conversationId;
|
||||
this.refreshProjectGitSummary?.();
|
||||
this.fetchTerminalCount();
|
||||
this.currentConversationTitle = result.title;
|
||||
this.titleReady = true;
|
||||
this.suppressTitleTyping = false;
|
||||
this.startTitleTyping(this.currentConversationTitle, { animate: false });
|
||||
if (!preserveListPosition) {
|
||||
this.promoteConversationToTop(conversationId);
|
||||
}
|
||||
const urlPrefix = this.multiAgentMode ? '/multiagent/' : '/';
|
||||
history.pushState(
|
||||
{ conversationId },
|
||||
'',
|
||||
`${urlPrefix}${this.stripConversationPrefix(conversationId)}`
|
||||
);
|
||||
this.skipConversationLoadedEvent = true;
|
||||
|
||||
// 3. 重置UI状态
|
||||
this.resetAllStates(`loadConversation:${conversationId}`);
|
||||
this.subAgentFetch();
|
||||
this.fetchTodoList();
|
||||
|
||||
// 4. 立即加载历史和统计,确保列表切换后界面同步更新
|
||||
await this.fetchAndDisplayHistory();
|
||||
await this.refreshRunningWorkspaceTasks?.();
|
||||
const visibleTask =
|
||||
typeof this.getVisibleWorkspaceTaskForConversation === 'function'
|
||||
? this.getVisibleWorkspaceTaskForConversation(conversationId)
|
||||
: null;
|
||||
const visibleTaskStatus = String(visibleTask?.status || '');
|
||||
const activeStatuses = new Set(['pending', 'running', 'cancel_requested']);
|
||||
const terminalStatuses = new Set(['succeeded', 'failed', 'canceled']);
|
||||
if (visibleTask && activeStatuses.has(visibleTaskStatus)) {
|
||||
// 切回正在运行的对话时,必须恢复该任务的 REST 轮询和输入区忙碌态;
|
||||
// 否则只切换了历史视图,后续输出不会续接到当前对话,发送按钮也不会变成停止按钮。
|
||||
await this.restoreTaskState?.();
|
||||
} else if (visibleTask && terminalStatuses.has(visibleTaskStatus)) {
|
||||
if (visibleTask && terminalStatuses.has(visibleTaskStatus)) {
|
||||
// 用户已点进完成后的“待查看”对话,清除列表里的完成标记。
|
||||
this.acknowledgeCompletedWorkspaceTask?.(visibleTask.task_id);
|
||||
}
|
||||
|
||||
@ -93,7 +93,10 @@ export const compressionMethods = {
|
||||
duration: 2400
|
||||
});
|
||||
},
|
||||
async restoreTaskState() {
|
||||
async restoreTaskState(options = {}) {
|
||||
// 统一加载协议快速路径:bootstrap 已聚合任务摘要/全量事件/判据输入,
|
||||
// 跳过 GET /api/tasks、历史死等与 GET /api/tasks/{id} 三次请求,行为与原有逻辑等价。
|
||||
const bootstrapReplay = options && options.bootstrapReplay ? options.bootstrapReplay : null;
|
||||
// 清理已处理的事件索引
|
||||
this.clearProcessedEvents();
|
||||
// 启动运行状态对账循环(幂等):事件负责即时性,对账负责正确性
|
||||
@ -128,8 +131,26 @@ export const compressionMethods = {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找运行中的任务
|
||||
const runningTask = await taskStore.loadRunningTask(this.currentConversationId);
|
||||
// 查找运行中的任务(快速路径:复用 bootstrap 任务摘要,
|
||||
// 复刻 loadRunningTask 的 store 设置与续播偏移量)
|
||||
let runningTask;
|
||||
if (bootstrapReplay && bootstrapReplay.task && bootstrapReplay.task.task_id) {
|
||||
runningTask = bootstrapReplay.task;
|
||||
taskStore.currentTaskId = runningTask.task_id;
|
||||
taskStore.taskStatus = runningTask.status;
|
||||
taskStore.taskCreatedAt = runningTask.created_at;
|
||||
taskStore.taskUpdatedAt = runningTask.updated_at;
|
||||
taskStore.pollingError = null;
|
||||
taskStore.pollingErrorCount = 0;
|
||||
taskStore.pollingWarned = false;
|
||||
taskStore.runtimeQueueSnapshotKey = '';
|
||||
taskStore.lastEventIndex =
|
||||
typeof bootstrapReplay.replay_from === 'number'
|
||||
? bootstrapReplay.replay_from
|
||||
: (Array.isArray(bootstrapReplay.events) ? bootstrapReplay.events.length : 0);
|
||||
} else {
|
||||
runningTask = await taskStore.loadRunningTask(this.currentConversationId);
|
||||
}
|
||||
|
||||
if (!runningTask) {
|
||||
restoreDebugLog('restore:no-running-task', {
|
||||
@ -190,12 +211,12 @@ export const compressionMethods = {
|
||||
currentConversationId: this.currentConversationId
|
||||
});
|
||||
|
||||
// 检查历史是否已加载
|
||||
// 检查历史是否已加载(快速路径:历史已由 enterConversation 渲染,跳过死等)
|
||||
const hasMessages = Array.isArray(this.messages) && this.messages.length > 0;
|
||||
const historyLoadingSameConversation =
|
||||
!!this.historyLoading && this.historyLoadingFor === this.currentConversationId;
|
||||
|
||||
if (!hasMessages) {
|
||||
if (!hasMessages && !bootstrapReplay) {
|
||||
this._restoreTaskStateWaitCount = (this._restoreTaskStateWaitCount || 0) + 1;
|
||||
const waitedTooLong = this._restoreTaskStateWaitCount >= 8; // ~4s
|
||||
restoreDebugLog('restore:history-empty', {
|
||||
@ -233,25 +254,30 @@ export const compressionMethods = {
|
||||
historyLoadingFor: this.historyLoadingFor
|
||||
});
|
||||
|
||||
// 获取任务的所有事件
|
||||
const detailResponse = await fetch(`/api/tasks/${taskStore.currentTaskId}`);
|
||||
if (!detailResponse.ok) {
|
||||
debugLog('[TaskPolling] 获取任务详情失败');
|
||||
return;
|
||||
}
|
||||
// 获取任务的所有事件(快速路径:bootstrap 已聚合全量事件,跳过详情请求)
|
||||
let allEvents;
|
||||
if (bootstrapReplay) {
|
||||
allEvents = Array.isArray(bootstrapReplay.events) ? bootstrapReplay.events : [];
|
||||
} else {
|
||||
const detailResponse = await fetch(`/api/tasks/${taskStore.currentTaskId}`);
|
||||
if (!detailResponse.ok) {
|
||||
debugLog('[TaskPolling] 获取任务详情失败');
|
||||
return;
|
||||
}
|
||||
|
||||
const detailResult = await detailResponse.json();
|
||||
if (!detailResult.success || !detailResult.data.events) {
|
||||
restoreDebugLog('restore:task-detail-invalid', {
|
||||
ok: !!detailResult?.success,
|
||||
hasData: !!detailResult?.data,
|
||||
hasEvents: !!detailResult?.data?.events
|
||||
});
|
||||
debugLog('[TaskPolling] 任务详情无效');
|
||||
return;
|
||||
}
|
||||
const detailResult = await detailResponse.json();
|
||||
if (!detailResult.success || !detailResult.data.events) {
|
||||
restoreDebugLog('restore:task-detail-invalid', {
|
||||
ok: !!detailResult?.success,
|
||||
hasData: !!detailResult?.data,
|
||||
hasEvents: !!detailResult?.data?.events
|
||||
});
|
||||
debugLog('[TaskPolling] 任务详情无效');
|
||||
return;
|
||||
}
|
||||
|
||||
const allEvents = detailResult.data.events;
|
||||
allEvents = detailResult.data.events;
|
||||
}
|
||||
debugLog(`[TaskPolling] 获取到 ${allEvents.length} 个事件`);
|
||||
restoreDebugLog('restore:task-detail-events', {
|
||||
total: allEvents.length,
|
||||
@ -273,56 +299,66 @@ export const compressionMethods = {
|
||||
// });
|
||||
|
||||
// 先分析事件状态(用于判定是否强制重建)
|
||||
// 快速路径:分析输入直接取自 bootstrap 的服务端判据(语义与下方循环等价)
|
||||
const injectedDecision = bootstrapReplay ? bootstrapReplay.decision_inputs || null : null;
|
||||
let inThinking = false;
|
||||
let inText = false;
|
||||
let hasTextChunkEvent = false;
|
||||
let hasAssistantResponseEvent = false;
|
||||
let hasAssistantContentEvent = false;
|
||||
|
||||
for (let i = 0; i < allEvents.length; i++) {
|
||||
const event = allEvents[i];
|
||||
const eventType = String(event?.type || '');
|
||||
if (
|
||||
[
|
||||
'ai_message_start',
|
||||
'thinking_start',
|
||||
'thinking_chunk',
|
||||
'thinking_end',
|
||||
'text_start',
|
||||
'text_chunk',
|
||||
'text_end',
|
||||
'tool_preparing',
|
||||
'tool_start',
|
||||
'tool_update',
|
||||
'tool_complete'
|
||||
].includes(eventType)
|
||||
) {
|
||||
hasAssistantResponseEvent = true;
|
||||
}
|
||||
if (
|
||||
[
|
||||
'thinking_chunk',
|
||||
'text_chunk',
|
||||
'tool_preparing',
|
||||
'tool_start',
|
||||
'tool_update',
|
||||
'tool_complete'
|
||||
].includes(eventType)
|
||||
) {
|
||||
hasAssistantContentEvent = true;
|
||||
}
|
||||
if (event.type === 'thinking_start') {
|
||||
inThinking = true;
|
||||
} else if (event.type === 'thinking_end') {
|
||||
inThinking = false;
|
||||
}
|
||||
if (event.type === 'text_start') {
|
||||
inText = true;
|
||||
} else if (event.type === 'text_end') {
|
||||
inText = false;
|
||||
}
|
||||
if (event.type === 'text_chunk') {
|
||||
hasTextChunkEvent = true;
|
||||
if (injectedDecision) {
|
||||
inThinking = !!injectedDecision.in_thinking;
|
||||
inText = !!injectedDecision.in_text;
|
||||
hasTextChunkEvent = !!injectedDecision.has_text_chunk_event;
|
||||
hasAssistantResponseEvent = !!injectedDecision.has_assistant_response_event;
|
||||
hasAssistantContentEvent = !!injectedDecision.has_assistant_content_event;
|
||||
} else {
|
||||
for (let i = 0; i < allEvents.length; i++) {
|
||||
const event = allEvents[i];
|
||||
const eventType = String(event?.type || '');
|
||||
if (
|
||||
[
|
||||
'ai_message_start',
|
||||
'thinking_start',
|
||||
'thinking_chunk',
|
||||
'thinking_end',
|
||||
'text_start',
|
||||
'text_chunk',
|
||||
'text_end',
|
||||
'tool_preparing',
|
||||
'tool_start',
|
||||
'tool_update',
|
||||
'tool_complete'
|
||||
].includes(eventType)
|
||||
) {
|
||||
hasAssistantResponseEvent = true;
|
||||
}
|
||||
if (
|
||||
[
|
||||
'thinking_chunk',
|
||||
'text_chunk',
|
||||
'tool_preparing',
|
||||
'tool_start',
|
||||
'tool_update',
|
||||
'tool_complete'
|
||||
].includes(eventType)
|
||||
) {
|
||||
hasAssistantContentEvent = true;
|
||||
}
|
||||
if (event.type === 'thinking_start') {
|
||||
inThinking = true;
|
||||
} else if (event.type === 'thinking_end') {
|
||||
inThinking = false;
|
||||
}
|
||||
if (event.type === 'text_start') {
|
||||
inText = true;
|
||||
} else if (event.type === 'text_end') {
|
||||
inText = false;
|
||||
}
|
||||
if (event.type === 'text_chunk') {
|
||||
hasTextChunkEvent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -58,23 +58,17 @@ export const routeMethods = {
|
||||
const convPart = path.slice('multiagent/'.length);
|
||||
const convId = convPart.startsWith('conv_') ? convPart : `conv_${convPart}`;
|
||||
try {
|
||||
const resp = await fetch(`/api/conversations/${convId}/load`, { method: 'PUT' });
|
||||
const result = await resp.json();
|
||||
// 统一加载协议:一次 bootstrap 拿元数据+历史+运行状态(纯只读,不切换后端上下文)
|
||||
const result = await this.enterConversation(convId, {
|
||||
source: 'refresh',
|
||||
urlMode: 'replace',
|
||||
urlPrefix: '/multiagent/'
|
||||
});
|
||||
if (result.success) {
|
||||
if (typeof result.run_mode === 'string') {
|
||||
this.runMode = result.run_mode;
|
||||
this.thinkingMode = typeof result.thinking_mode === 'boolean' ? result.thinking_mode : result.run_mode !== 'fast';
|
||||
} else if (typeof result.thinking_mode === 'boolean') {
|
||||
this.thinkingMode = result.thinking_mode;
|
||||
this.runMode = result.thinking_mode ? 'thinking' : 'fast';
|
||||
}
|
||||
if (typeof result.model_key === 'string' && result.model_key) this.modelSet(result.model_key);
|
||||
this.currentConversationId = convId;
|
||||
this.currentConversationTitle = result.title || '多智能体模式';
|
||||
this.titleReady = true;
|
||||
this.suppressTitleTyping = false;
|
||||
this.startTitleTyping(this.currentConversationTitle, { animate: false });
|
||||
history.replaceState({ conversationId: convId }, '', `/multiagent/${this.stripConversationPrefix(convId)}`);
|
||||
this.initialRouteResolved = true;
|
||||
await this.restoreComposerDraftState('bootstrap-route:multiagent-existing');
|
||||
return;
|
||||
@ -105,32 +99,17 @@ export const routeMethods = {
|
||||
|
||||
const convId = path.startsWith('conv_') ? path : `conv_${path}`;
|
||||
try {
|
||||
const resp = await fetch(`/api/conversations/${convId}/load`, { method: 'PUT' });
|
||||
const result = await resp.json();
|
||||
// 统一加载协议:一次 bootstrap 拿元数据+历史+运行状态(纯只读,不切换后端上下文)
|
||||
const result = await this.enterConversation(convId, {
|
||||
source: 'refresh',
|
||||
urlMode: 'replace',
|
||||
urlPrefix: '/'
|
||||
});
|
||||
if (result.success) {
|
||||
if (typeof result.run_mode === 'string') {
|
||||
this.runMode = result.run_mode;
|
||||
this.thinkingMode =
|
||||
typeof result.thinking_mode === 'boolean'
|
||||
? result.thinking_mode
|
||||
: result.run_mode !== 'fast';
|
||||
} else if (typeof result.thinking_mode === 'boolean') {
|
||||
this.thinkingMode = result.thinking_mode;
|
||||
this.runMode = result.thinking_mode ? 'thinking' : 'fast';
|
||||
}
|
||||
if (typeof result.model_key === 'string' && result.model_key) {
|
||||
this.modelSet(result.model_key);
|
||||
}
|
||||
this.currentConversationId = convId;
|
||||
this.currentConversationTitle = result.title || '';
|
||||
this.titleReady = true;
|
||||
this.suppressTitleTyping = false;
|
||||
this.startTitleTyping(this.currentConversationTitle, { animate: false });
|
||||
history.replaceState(
|
||||
{ conversationId: convId },
|
||||
'',
|
||||
`/${this.stripConversationPrefix(convId)}`
|
||||
);
|
||||
} else {
|
||||
history.replaceState({}, '', '/new');
|
||||
this.currentConversationId = null;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user