Merge pull request #1391 from pikasTech/codex/1389-fast-new-session
加速 Workbench 新建会话切换
This commit is contained in:
@@ -23,6 +23,11 @@ interface HydrateOptions {
|
||||
conversationId?: string | null;
|
||||
}
|
||||
|
||||
interface SessionCreatePersistenceResult {
|
||||
ok: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
const projectId = ref(resolveInitialWorkbenchProjectId());
|
||||
const workspace = ref<WorkspaceRecord | null>(window.HWLAB_CLOUD_WEB_WORKSPACE_BOOTSTRAP?.workspace ?? null);
|
||||
@@ -50,6 +55,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
const traceAuthorityById = computed(() => selectTraceAuthorityById(serverState.value));
|
||||
const traceHydrationInFlight = new Set<string>();
|
||||
const terminalRealtimeRefreshInFlight = new Set<string>();
|
||||
const pendingSessionCreates = new Map<string, Promise<SessionCreatePersistenceResult>>();
|
||||
let realtimeStream: WorkbenchEventStream | null = null;
|
||||
let realtimeKey = "";
|
||||
let realtimeGapTimer: number | null = null;
|
||||
@@ -154,24 +160,24 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
const current = await ensureWorkspace();
|
||||
if (!current) return;
|
||||
const requestEpoch = beginWorkspaceSelection();
|
||||
loading.value = true;
|
||||
const response = await api.workbench.selectConversation(current.workspaceId, { projectId: activeProjectId.value, create: true, updatedByClient: "cloud-web-vue" });
|
||||
const selectedProjectId = activeProjectId.value;
|
||||
const optimisticConversation = makeOptimisticConversation(selectedProjectId);
|
||||
setActiveConversationSelection(optimisticConversation.conversationId);
|
||||
conversations.value = mergeConversationIntoList(conversations.value, optimisticConversation);
|
||||
rememberConversationDetail(optimisticConversation);
|
||||
conversationsReady.value = true;
|
||||
workspace.value = workspaceWithSelectedConversation(current, optimisticConversation, selectedProjectId);
|
||||
rememberWorkspaceSnapshot(selectedProjectId, workspace.value);
|
||||
messages.value = [];
|
||||
currentRequest.value = null;
|
||||
loading.value = false;
|
||||
if (response.ok && isCurrentWorkspaceSelection(requestEpoch)) {
|
||||
workspace.value = response.data?.workspace ?? current;
|
||||
rememberWorkspaceSnapshot(activeProjectId.value, workspace.value);
|
||||
const selectedConversationId = selectedConversationIdFromWorkspace(workspace.value);
|
||||
const selectedConversation = workspace.value?.selectedConversation ?? null;
|
||||
setActiveConversationSelection(selectedConversationId);
|
||||
rememberConversationDetail(selectedConversation);
|
||||
messages.value = restoreMessagesTraceAuthority(messagesFromSelectedConversation(selectedConversation, selectedConversationId, null, []), messages.value);
|
||||
currentRequest.value = null;
|
||||
void hydrateTurnStatusAuthority(messages.value);
|
||||
void hydrateTraceEvents(messages.value);
|
||||
await refreshSelectedSessionStatus();
|
||||
await refreshConversations(selectedConversationIdFromWorkspace(workspace.value));
|
||||
restartRealtime("create-session");
|
||||
}
|
||||
error.value = null;
|
||||
restartRealtime("create-session-optimistic");
|
||||
const persistence = persistOptimisticSession(optimisticConversation, selectedProjectId, current.workspaceId, requestEpoch);
|
||||
pendingSessionCreates.set(optimisticConversation.conversationId, persistence);
|
||||
void persistence.then((result) => {
|
||||
if (result.ok && pendingSessionCreates.get(optimisticConversation.conversationId) === persistence) pendingSessionCreates.delete(optimisticConversation.conversationId);
|
||||
});
|
||||
}
|
||||
|
||||
async function selectConversation(conversation: ConversationRecord): Promise<void> {
|
||||
@@ -295,6 +301,54 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
reduceServerState({ type: "conversation.list", conversations: source });
|
||||
}
|
||||
|
||||
async function persistOptimisticSession(conversation: ConversationRecord, selectedProjectId: string, workspaceId: string, requestEpoch: number): Promise<SessionCreatePersistenceResult> {
|
||||
try {
|
||||
const response = await api.workbench.selectConversation(workspaceId, {
|
||||
projectId: selectedProjectId,
|
||||
create: true,
|
||||
conversationId: conversation.conversationId,
|
||||
sessionId: conversation.sessionId,
|
||||
threadId: conversation.threadId,
|
||||
updatedByClient: "cloud-web-vue-fast-create"
|
||||
});
|
||||
if (!response.ok || !response.data?.workspace) {
|
||||
if (activeConversationId.value === conversation.conversationId) error.value = response.error ?? "session create failed";
|
||||
return { ok: false, error: response.error ?? "session create failed" };
|
||||
}
|
||||
const persistedWorkspace = response.data.workspace;
|
||||
const persistedConversation = persistedWorkspace.selectedConversation?.conversationId === conversation.conversationId
|
||||
? persistedWorkspace.selectedConversation
|
||||
: conversation;
|
||||
rememberConversationDetail(persistedConversation);
|
||||
conversations.value = mergeConversationIntoList(conversations.value, persistedConversation);
|
||||
if (activeConversationId.value === conversation.conversationId) {
|
||||
const persistedProjectId = conversationProjectId(persistedConversation, selectedProjectId);
|
||||
workspace.value = workspaceWithSelectedConversation(persistedWorkspace, persistedConversation, persistedProjectId);
|
||||
rememberWorkspaceSnapshot(persistedProjectId, workspace.value);
|
||||
error.value = null;
|
||||
if (messages.value.length === 0) {
|
||||
messages.value = restoreMessagesTraceAuthority(messagesFromSelectedConversation(persistedConversation, conversation.conversationId, null, []), messages.value);
|
||||
}
|
||||
void hydrateTurnStatusAuthority(messages.value);
|
||||
void hydrateTraceEvents(messages.value);
|
||||
void refreshSelectedSessionStatus(persistedConversation);
|
||||
void refreshConversations(conversation.conversationId);
|
||||
restartRealtime(requestEpoch === workspaceSelectionEpoch.value ? "create-session" : "create-session-persisted");
|
||||
}
|
||||
return { ok: true, error: null };
|
||||
} catch (cause) {
|
||||
const message = cause instanceof Error ? cause.message : String(cause);
|
||||
if (activeConversationId.value === conversation.conversationId) error.value = message;
|
||||
return { ok: false, error: message };
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForPendingSessionCreate(conversationId: string): Promise<SessionCreatePersistenceResult> {
|
||||
const pending = pendingSessionCreates.get(conversationId);
|
||||
if (!pending) return { ok: true, error: null };
|
||||
return pending;
|
||||
}
|
||||
|
||||
function setActiveConversationSelection(conversationId: string | null | undefined): void {
|
||||
explicitConversationId.value = nextExplicitWorkbenchConversationId(explicitConversationId.value, conversationId);
|
||||
}
|
||||
@@ -424,6 +478,13 @@ export const useWorkbenchStore = defineStore("workbench", () => {
|
||||
currentRequest.value = { traceId, conversationId, sessionId, threadId, status: "running" };
|
||||
rememberRecentDraft(value);
|
||||
recordActivity("submit");
|
||||
const sessionCreate = await waitForPendingSessionCreate(conversationId);
|
||||
if (!sessionCreate.ok) {
|
||||
markMessage(traceId, { status: "failed", text: `新会话尚未准备好,消息未提交:${sessionCreate.error ?? "session create failed"}` });
|
||||
chatPending.value = false;
|
||||
currentRequest.value = null;
|
||||
return;
|
||||
}
|
||||
const payload = { projectId: activeProjectId.value, message: value, prompt: value, conversationId, sessionId, threadId, traceId, providerProfile: providerProfile.value, gatewayShellTimeoutMs: gatewayShellTimeoutMs.value, workspaceId: workspace.value?.workspaceId, workspaceRevision: workspace.value?.revision, targetTraceId: composer.value.targetTraceId, steerTraceId };
|
||||
const route = steerMode ? api.agent.steerAgentMessage : api.agent.sendAgentMessage;
|
||||
const response = await route(payload, codeAgentTimeoutMs.value, () => activityRef.value);
|
||||
@@ -1030,6 +1091,33 @@ function sessionIdFromConversation(conversation: ConversationRecord | null | und
|
||||
return firstNonEmptyString(conversation?.sessionId, conversation?.session?.sessionId) ?? null;
|
||||
}
|
||||
|
||||
function makeOptimisticConversation(projectId: string, now = new Date()): ConversationRecord {
|
||||
const token = nextProtocolId("cnv").slice(4);
|
||||
const title = `新会话 ${shortSessionTimestamp(now)}`;
|
||||
const createdAt = now.toISOString();
|
||||
const sessionId = `ses_${token}`;
|
||||
const threadId = `thr_${token}`;
|
||||
return {
|
||||
conversationId: `cnv_${token}`,
|
||||
projectId,
|
||||
sessionId,
|
||||
threadId,
|
||||
status: "active",
|
||||
title,
|
||||
name: title,
|
||||
startedAt: createdAt,
|
||||
updatedAt: createdAt,
|
||||
messageCount: 0,
|
||||
session: { sessionId, threadId, status: "active" },
|
||||
messages: []
|
||||
};
|
||||
}
|
||||
|
||||
function shortSessionTimestamp(value: Date): string {
|
||||
const pad = (number: number): string => String(number).padStart(2, "0");
|
||||
return `${pad(value.getHours())}:${pad(value.getMinutes())}:${pad(value.getSeconds())}`;
|
||||
}
|
||||
|
||||
function uniqueSessionIds(conversations: ConversationRecord[]): string[] {
|
||||
return [...new Set(conversations.map((conversation) => sessionIdFromConversation(conversation)).filter((sessionId): sessionId is string => Boolean(sessionId)))];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user