diff --git a/web/hwlab-cloud-web/src/stores/workbench.ts b/web/hwlab-cloud-web/src/stores/workbench.ts index f1a96c37..2f8b2239 100644 --- a/web/hwlab-cloud-web/src/stores/workbench.ts +++ b/web/hwlab-cloud-web/src/stores/workbench.ts @@ -153,29 +153,23 @@ export const useWorkbenchStore = defineStore("workbench", () => { workspace.value = optimisticWorkspaceSelection(current, conversation, tabProjectId); void refreshSessionStatusById(sessionIdFromConversation(conversation)); loading.value = true; - const [response, detailResponse] = await Promise.all([ - api.workbench.selectConversation(current.workspaceId, { projectId: tabProjectId, conversationId: conversation.conversationId, sessionId: conversation.sessionId, threadId: conversation.threadId, updatedByClient: "cloud-web-vue" }), - api.workbench.conversation(conversation.conversationId, { projectId: tabProjectId }) - ]); + const persistPromise = api.workbench.selectConversation(current.workspaceId, { projectId: tabProjectId, conversationId: conversation.conversationId, sessionId: conversation.sessionId, threadId: conversation.threadId, updatedByClient: "cloud-web-vue" }); + const detailResponse = await api.workbench.conversation(conversation.conversationId, { projectId: tabProjectId }); + const response = await persistPromise; loading.value = false; clearSwitchingConversation(conversation.conversationId); if (!isCurrentWorkspaceSelection(requestEpoch, conversation.conversationId)) return; - if (response.ok) { - const selectedConversation = detailResponse.ok ? detailResponse.data?.conversation ?? null : null; - if (!selectedConversation) error.value = detailResponse.error ?? "conversation unavailable"; - const selectedProjectId = conversationProjectId(selectedConversation ?? conversation, tabProjectId); - workspace.value = workspaceWithSelectedConversation(response.data?.workspace ?? current, selectedConversation ?? conversation, selectedProjectId); - messages.value = restoreMessagesTraceAuthority(messagesFromSelectedConversation(selectedConversation, conversation.conversationId, null, []), messages.value); - if (selectedConversation) conversations.value = mergeConversationIntoList(conversations.value, selectedConversation); - void hydrateTurnStatusAuthority(messages.value); - void hydrateTerminalMessageDiagnostics(); - void hydrateTraceEvents(messages.value); - reattachRestoredActiveTrace(); - currentRequest.value = null; - await refreshSelectedSessionStatus(selectedConversation ?? conversation); + const selectedConversation = detailResponse.ok ? detailResponse.data?.conversation ?? null : null; + if (selectedConversation) { + const selectedProjectId = conversationProjectId(selectedConversation, tabProjectId); + applySelectedConversationDetail(selectedConversation, response.ok ? response.data?.workspace ?? current : workspace.value ?? current, selectedProjectId, null, messages.value); + error.value = null; + void persistSelectedConversation(selectedConversation, selectedProjectId, response); + await refreshSelectedSessionStatus(selectedConversation); await refreshConversations(conversation.conversationId); return; } + error.value = detailResponse.error ?? "conversation unavailable"; if (response.status === 404 && await retrySelectConversationWithFreshWorkspace(conversation, tabProjectId, requestEpoch)) return; error.value = response.error ?? "session switch failed"; } @@ -203,9 +197,14 @@ export const useWorkbenchStore = defineStore("workbench", () => { error.value = response.error ?? "session URL not found"; return false; } + const selectedProjectId = conversationProjectId(conversation, activeProjectId.value); + applySelectedConversationDetail(conversation, current, selectedProjectId, null, messages.value); + clearSwitchingConversation(normalized); + error.value = null; void refreshSessionStatusById(sessionIdFromConversation(conversation)); - await selectConversation(conversation); - return activeConversationId.value === normalized; + void persistSelectedConversation(conversation, selectedProjectId); + void refreshConversations(normalized); + return activeConversationId.value === normalized && messages.value.length > 0; } async function deleteCurrentSession(): Promise { @@ -685,6 +684,37 @@ export const useWorkbenchStore = defineStore("workbench", () => { if (switchingConversationId.value === conversationId) switchingConversationId.value = null; } + function applySelectedConversationDetail(conversation: ConversationRecord, baseWorkspace: WorkspaceRecord | null, selectedProjectId: string, previousConversationId: string | null, previousMessages: ChatMessage[]): void { + workspace.value = workspaceWithSelectedConversation(baseWorkspace, conversation, selectedProjectId); + messages.value = restoreMessagesTraceAuthority(messagesFromSelectedConversation(conversation, conversation.conversationId, previousConversationId, previousMessages), previousMessages); + conversations.value = mergeConversationIntoList(conversations.value, conversation); + conversationsReady.value = true; + currentRequest.value = null; + void hydrateTurnStatusAuthority(messages.value); + void hydrateTerminalMessageDiagnostics(); + void hydrateTraceEvents(messages.value); + reattachRestoredActiveTrace(); + } + + async function persistSelectedConversation(conversation: ConversationRecord, selectedProjectId: string, existingResponse?: ApiResult<{ workspace?: WorkspaceRecord }>): Promise { + const conversationId = conversation.conversationId; + let response = existingResponse ?? null; + if (!response) { + const workspaceId = workspace.value?.workspaceId; + if (!workspaceId) return; + response = await api.workbench.selectConversation(workspaceId, { projectId: selectedProjectId, conversationId, sessionId: conversation.sessionId, threadId: conversation.threadId, updatedByClient: "cloud-web-vue" }); + } + if (!response.ok && response.status === 404) { + const fresh = await api.workbench.workspace(selectedProjectId); + const freshWorkspace = fresh.data?.workspace; + if (fresh.ok && freshWorkspace?.workspaceId && activeConversationId.value === conversationId) { + response = await api.workbench.selectConversation(freshWorkspace.workspaceId, { projectId: selectedProjectId, conversationId, sessionId: conversation.sessionId, threadId: conversation.threadId, updatedByClient: "cloud-web-vue-retry" }); + } + } + if (!response.ok || !response.data?.workspace || activeConversationId.value !== conversationId) return; + workspace.value = workspaceWithSelectedConversation(response.data.workspace, conversation, selectedProjectId); + } + async function retrySelectConversationWithFreshWorkspace(conversation: ConversationRecord, tabProjectId: string, requestEpoch: number): Promise { const fresh = await api.workbench.workspace(tabProjectId); if (!isCurrentWorkspaceSelection(requestEpoch, conversation.conversationId)) return true;