diff --git a/web/hwlab-cloud-web/src/api/workbench.ts b/web/hwlab-cloud-web/src/api/workbench.ts index e93217bc..27ef80e8 100644 --- a/web/hwlab-cloud-web/src/api/workbench.ts +++ b/web/hwlab-cloud-web/src/api/workbench.ts @@ -3,18 +3,23 @@ import type { ApiResult, ConversationRecord, WorkspaceRecord } from "@/types"; export interface ConversationListOptions { includeConversationId?: string | null; + timeoutMs?: number | null; } export interface ConversationDetailOptions { projectId?: string | null; + timeoutMs?: number | null; } +const CONVERSATION_LIST_TIMEOUT_MS = 30_000; +const CONVERSATION_DETAIL_TIMEOUT_MS = 45_000; + export const workbenchAPI = { workspace: (projectId: string): Promise> => fetchJson(`/v1/workbench/workspace?projectId=${encodeURIComponent(projectId)}`, { timeoutMs: 8000, timeoutName: "workspace" }), updateWorkspace: (workspaceId: string, payload: Record): Promise> => fetchJson(`/v1/workbench/workspace/${encodeURIComponent(workspaceId)}`, { method: "PATCH", body: JSON.stringify(payload), timeoutMs: 8000, timeoutName: "workspace update" }), selectConversation: (workspaceId: string, payload: Record): Promise> => fetchJson(`/v1/workbench/workspace/${encodeURIComponent(workspaceId)}/select-conversation`, { method: "POST", body: JSON.stringify(payload), timeoutMs: 8000, timeoutName: "select conversation" }), - conversations: (projectId: string, options: ConversationListOptions = {}): Promise> => fetchJson(conversationListPath(projectId, options), { timeoutMs: 8000, timeoutName: "conversations" }), - conversation: (conversationId: string, options: ConversationDetailOptions = {}): Promise> => fetchJson(conversationDetailPath(conversationId, options), { timeoutMs: 8000, timeoutName: "conversation" }), + conversations: (projectId: string, options: ConversationListOptions = {}): Promise> => fetchJson(conversationListPath(projectId, options), { timeoutMs: requestTimeoutMs(options.timeoutMs, CONVERSATION_LIST_TIMEOUT_MS), timeoutName: "conversations" }), + conversation: (conversationId: string, options: ConversationDetailOptions = {}): Promise> => fetchJson(conversationDetailPath(conversationId, options), { timeoutMs: requestTimeoutMs(options.timeoutMs, CONVERSATION_DETAIL_TIMEOUT_MS), timeoutName: "conversation" }), deleteConversation: (conversationId: string, payload: Record): Promise> => fetchJson(`/v1/agent/conversations/${encodeURIComponent(conversationId)}`, { method: "DELETE", body: JSON.stringify(payload), timeoutMs: 8000, timeoutName: "delete conversation" }), saveConversation: (conversationId: string, payload: Record): Promise> => fetchJson(`/v1/agent/conversations/${encodeURIComponent(conversationId)}`, { method: "PUT", body: JSON.stringify(payload), timeoutMs: 8000, timeoutName: "save conversation" }) }; @@ -33,3 +38,8 @@ function conversationListPath(projectId: string, options: ConversationListOption if (includeConversationId) params.set("includeConversationId", includeConversationId); return `/v1/agent/conversations?${params.toString()}`; } + +function requestTimeoutMs(value: number | null | undefined, fallback: number): number { + const timeout = Number(value); + return Number.isFinite(timeout) && timeout > 0 ? Math.trunc(timeout) : fallback; +} diff --git a/web/hwlab-cloud-web/src/stores/workbench.ts b/web/hwlab-cloud-web/src/stores/workbench.ts index cf6ef673..f1a96c37 100644 --- a/web/hwlab-cloud-web/src/stores/workbench.ts +++ b/web/hwlab-cloud-web/src/stores/workbench.ts @@ -74,26 +74,9 @@ export const useWorkbenchStore = defineStore("workbench", () => { projectId.value = nextProjectId; const routeConversationId = normalizeWorkbenchConversationId(options.conversationId); const selectedConversationId = routeConversationId ?? selectedConversationIdFromWorkspace(nextWorkspace); - const [conversationsResult, selectedConversationResult] = await Promise.all([ - api.workbench.conversations(nextProjectId, { includeConversationId: selectedConversationId }), - selectedConversationId ? api.workbench.conversation(selectedConversationId, { projectId: nextProjectId }) : Promise.resolve(null) - ]); - loading.value = false; + const conversationsPromise = api.workbench.conversations(nextProjectId, { includeConversationId: selectedConversationId }); + const selectedConversationResult = selectedConversationId ? await api.workbench.conversation(selectedConversationId, { projectId: nextProjectId }) : null; const selectedConversation = selectedConversationResult?.ok ? selectedConversationResult.data?.conversation ?? null : null; - let nextConversations = conversations.value; - if (conversationsResult.ok) { - nextConversations = stableConversationList(conversations.value, conversationsResult.data?.conversations, selectedConversationId, selectedConversation); - conversations.value = nextConversations; - conversationsReady.value = true; - void refreshSessionStatusAuthority(nextConversations); - } else { - if (selectedConversation) { - nextConversations = mergeConversationIntoList(nextConversations, selectedConversation); - conversations.value = nextConversations; - } - conversationsReady.value = conversations.value.length > 0; - error.value = conversations.value.length > 0 ? null : conversationsResult.error ?? "session list unavailable"; - } if (selectedConversationId && selectedConversationResult && !selectedConversationResult.ok) error.value = selectedConversationResult.error ?? "conversation unavailable"; const workspaceSnapshot = selectedConversation ? workspaceWithSelectedConversation(nextWorkspace, selectedConversation, conversationProjectId(selectedConversation, nextProjectId)) @@ -110,6 +93,22 @@ export const useWorkbenchStore = defineStore("workbench", () => { void hydrateTraceEvents(messages.value); reattachRestoredActiveTrace(); } + loading.value = false; + const conversationsResult = await conversationsPromise; + let nextConversations = conversations.value; + if (conversationsResult.ok) { + nextConversations = stableConversationList(conversations.value, conversationsResult.data?.conversations, selectedConversationId, selectedConversation); + conversations.value = nextConversations; + conversationsReady.value = true; + void refreshSessionStatusAuthority(nextConversations); + } else { + if (selectedConversation) { + nextConversations = mergeConversationIntoList(nextConversations, selectedConversation); + conversations.value = nextConversations; + } + conversationsReady.value = conversations.value.length > 0; + error.value = conversations.value.length > 0 ? null : conversationsResult.error ?? "session list unavailable"; + } await refreshProviderOptions(); }