681 lines
23 KiB
TypeScript
681 lines
23 KiB
TypeScript
import { create } from 'zustand';
|
|
import {
|
|
createImageWorkspaceOperationId,
|
|
createImageWorkspaceProject,
|
|
deleteImageWorkspaceProject,
|
|
fetchImageWorkspace,
|
|
fetchImageWorkspaceProject,
|
|
ImageWorkspaceApiError,
|
|
openImageWorkspaceEvents,
|
|
renameImageWorkspaceProject,
|
|
submitImageWorkspaceCommand,
|
|
} from '@/lib/image-workspace';
|
|
import { setDesktopBackgroundLease } from '@/lib/host-api';
|
|
import { useAuthStore } from '@/stores/auth';
|
|
import {
|
|
IMAGE_WORKSPACE_UNAVAILABLE_CODE,
|
|
type DesignCommandInput,
|
|
type DesignCompilationIssue,
|
|
type DesignDeleteWorkspaceResult,
|
|
type DesignFieldChange,
|
|
type DesignGenerationTask,
|
|
type DesignUserFieldOperation,
|
|
type DesignWorkspace,
|
|
type DesignWorkspaceBootstrap,
|
|
type DesignWorkspaceEvent,
|
|
type DesignWorkspaceSummary,
|
|
} from '../../shared/image-workspace';
|
|
|
|
export type ImageWorkspaceLoadStatus =
|
|
| 'idle'
|
|
| 'loading'
|
|
| 'ready'
|
|
| 'unavailable'
|
|
| 'error'
|
|
| 'auth-required';
|
|
|
|
export type ImageWorkspaceEventState = 'idle' | 'connecting' | 'connected' | 'degraded';
|
|
|
|
export type PendingDesignOperation = {
|
|
id: string;
|
|
label: string;
|
|
command: DesignCommandInput;
|
|
status: 'submitting' | 'unknown';
|
|
error: string | null;
|
|
clearDraftPaths: string[];
|
|
clearChatDraft: boolean;
|
|
};
|
|
|
|
type ExecuteCommandOptions = {
|
|
label: string;
|
|
clearDraftPaths?: string[];
|
|
clearChatDraft?: boolean;
|
|
};
|
|
|
|
type ImageWorkspaceState = {
|
|
status: ImageWorkspaceLoadStatus;
|
|
bootstrap: DesignWorkspaceBootstrap | null;
|
|
activeWorkspaceId: string | null;
|
|
workspace: DesignWorkspace | null;
|
|
deletingWorkspaceId: string | null;
|
|
eventState: ImageWorkspaceEventState;
|
|
lastEventId: string | null;
|
|
pendingOperations: Record<string, PendingDesignOperation>;
|
|
fieldDrafts: Record<string, unknown>;
|
|
chatDraft: string;
|
|
assistantStreams: Record<string, string>;
|
|
quoteBlockers: DesignCompilationIssue[];
|
|
error: string | null;
|
|
load: () => Promise<DesignWorkspaceBootstrap | null>;
|
|
createProject: (title: string) => Promise<DesignWorkspace>;
|
|
renameProject: (workspaceId: string, title: string) => Promise<DesignWorkspace>;
|
|
deleteProject: (workspaceId: string) => Promise<DesignDeleteWorkspaceResult>;
|
|
selectProject: (workspaceId: string) => Promise<void>;
|
|
refreshWorkspace: () => Promise<DesignWorkspace | null>;
|
|
connectEvents: () => void;
|
|
disconnectEvents: () => void;
|
|
setFieldDraft: (path: string, value: unknown) => void;
|
|
discardFieldDraft: (path: string) => void;
|
|
setChatDraft: (value: string) => void;
|
|
applyFieldOperations: (
|
|
operations: DesignUserFieldOperation[],
|
|
clearDraftPaths?: string[],
|
|
) => Promise<DesignWorkspace>;
|
|
sendChat: () => Promise<DesignWorkspace>;
|
|
resolveDecisionPrompt: (
|
|
promptId: string,
|
|
action: 'select' | 'reject',
|
|
optionId?: string,
|
|
) => Promise<DesignWorkspace>;
|
|
requestQuote: () => Promise<DesignWorkspace>;
|
|
confirmGeneration: (quoteId: string) => Promise<DesignWorkspace>;
|
|
retryOperation: (clientOperationId: string) => Promise<DesignWorkspace>;
|
|
reset: () => void;
|
|
};
|
|
|
|
let inFlightLoad: Promise<DesignWorkspaceBootstrap | null> | null = null;
|
|
let activeEventSource: EventSource | null = null;
|
|
let activeEventWorkspaceId: string | null = null;
|
|
let selectionGeneration = 0;
|
|
|
|
function updateBackgroundLease(tasks: DesignGenerationTask[]): void {
|
|
const active = tasks.some((task) => task.status === 'queued' || task.status === 'running');
|
|
try {
|
|
void Promise.resolve(setDesktopBackgroundLease({
|
|
id: 'design-generation',
|
|
kind: 'design-generation',
|
|
active,
|
|
})).catch(() => undefined);
|
|
} catch {
|
|
// Renderer-only previews may not expose lifecycle IPC.
|
|
}
|
|
}
|
|
|
|
function closeEventSource(): void {
|
|
if (activeEventSource) {
|
|
activeEventSource.onopen = null;
|
|
activeEventSource.onerror = null;
|
|
activeEventSource.close();
|
|
}
|
|
activeEventSource = null;
|
|
activeEventWorkspaceId = null;
|
|
}
|
|
|
|
function messageForError(error: unknown): string {
|
|
if (error instanceof Error && error.message.trim()) return error.message;
|
|
return 'AI 设计暂时无法同步,请稍后重试';
|
|
}
|
|
|
|
function isAuthError(error: unknown): boolean {
|
|
return error instanceof ImageWorkspaceApiError && (error.status === 401 || error.status === 403);
|
|
}
|
|
|
|
function isUnavailableError(error: unknown): boolean {
|
|
return error instanceof ImageWorkspaceApiError
|
|
&& (error.status === 501 || error.code === IMAGE_WORKSPACE_UNAVAILABLE_CODE);
|
|
}
|
|
|
|
function isDirectionConflict(error: unknown): boolean {
|
|
return error instanceof ImageWorkspaceApiError
|
|
&& (error.status === 409
|
|
|| error.code === 'DESIGN_DIRECTION_REVISION_CONFLICT'
|
|
|| error.code === 'DIRECTION_REVISION_CONFLICT');
|
|
}
|
|
|
|
function parseWorkspaceEvent(event: Event): DesignWorkspaceEvent | null {
|
|
const data = (event as MessageEvent<unknown>).data;
|
|
if (typeof data !== 'string') return null;
|
|
try {
|
|
const parsed = JSON.parse(data) as Partial<DesignWorkspaceEvent>;
|
|
if (typeof parsed.id !== 'string' || typeof parsed.type !== 'string') return null;
|
|
if (![
|
|
'design.session.snapshot',
|
|
'design.assistant.delta',
|
|
'design.direction.updated',
|
|
'design.quote.blocked',
|
|
'design.workspace.updated',
|
|
].includes(parsed.type)) return null;
|
|
return parsed as DesignWorkspaceEvent;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function mergeTask(
|
|
tasks: DesignGenerationTask[],
|
|
incoming: DesignGenerationTask,
|
|
): DesignGenerationTask[] {
|
|
const existing = tasks.find((task) => task.taskId === incoming.taskId);
|
|
if (existing && existing.taskRevision > incoming.taskRevision) return tasks;
|
|
return [incoming, ...tasks.filter((task) => task.taskId !== incoming.taskId)]
|
|
.sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt));
|
|
}
|
|
|
|
function mergeSummary(
|
|
summaries: DesignWorkspaceSummary[],
|
|
summary: DesignWorkspaceSummary,
|
|
): DesignWorkspaceSummary[] {
|
|
return [summary, ...summaries.filter((item) => item.workspaceId !== summary.workspaceId)]
|
|
.sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt));
|
|
}
|
|
|
|
function withWorkspace(
|
|
state: ImageWorkspaceState,
|
|
workspace: DesignWorkspace,
|
|
): Partial<ImageWorkspaceState> {
|
|
updateBackgroundLease(workspace.tasks);
|
|
return {
|
|
status: 'ready',
|
|
activeWorkspaceId: workspace.workspace.workspaceId,
|
|
workspace,
|
|
bootstrap: state.bootstrap
|
|
? {
|
|
...state.bootstrap,
|
|
workspaces: mergeSummary(state.bootstrap.workspaces, workspace.workspace),
|
|
}
|
|
: state.bootstrap,
|
|
error: null,
|
|
};
|
|
}
|
|
|
|
function applyEventToWorkspace(
|
|
workspace: DesignWorkspace,
|
|
event: DesignWorkspaceEvent,
|
|
): DesignWorkspace {
|
|
if (event.type === 'design.session.snapshot' || event.type === 'design.direction.updated') {
|
|
return {
|
|
...workspace,
|
|
workspace: {
|
|
...workspace.workspace,
|
|
directionRevision: event.form.directionRevision,
|
|
specificationRevision: event.form.specificationRevision,
|
|
workspaceViewRevision: event.form.workspaceViewRevision,
|
|
},
|
|
form: event.form,
|
|
};
|
|
}
|
|
if (event.type === 'design.quote.blocked') {
|
|
return {
|
|
...workspace,
|
|
workspace: {
|
|
...workspace.workspace,
|
|
directionRevision: event.form.directionRevision,
|
|
specificationRevision: event.form.specificationRevision,
|
|
workspaceViewRevision: event.form.workspaceViewRevision,
|
|
},
|
|
form: event.form,
|
|
};
|
|
}
|
|
if (event.type === 'design.workspace.updated' && event.changedTask) {
|
|
return {
|
|
...workspace,
|
|
workspace: {
|
|
...workspace.workspace,
|
|
workspaceViewRevision: Math.max(
|
|
workspace.workspace.workspaceViewRevision,
|
|
event.workspaceViewRevision,
|
|
),
|
|
},
|
|
tasks: mergeTask(workspace.tasks, event.changedTask),
|
|
};
|
|
}
|
|
return workspace;
|
|
}
|
|
|
|
export const useImageWorkspaceStore = create<ImageWorkspaceState>((set, get) => {
|
|
const refreshWorkspaceById = async (workspaceId: string): Promise<DesignWorkspace> => {
|
|
const generation = ++selectionGeneration;
|
|
const workspace = await fetchImageWorkspaceProject(workspaceId);
|
|
if (generation !== selectionGeneration || get().activeWorkspaceId !== workspaceId) {
|
|
return workspace;
|
|
}
|
|
set((state) => withWorkspace(state, workspace));
|
|
return workspace;
|
|
};
|
|
|
|
const executeCommand = async (
|
|
command: DesignCommandInput,
|
|
options: ExecuteCommandOptions,
|
|
): Promise<DesignWorkspace> => {
|
|
const operation: PendingDesignOperation = {
|
|
id: command.clientOperationId,
|
|
label: options.label,
|
|
command,
|
|
status: 'submitting',
|
|
error: null,
|
|
clearDraftPaths: options.clearDraftPaths ?? [],
|
|
clearChatDraft: options.clearChatDraft ?? false,
|
|
};
|
|
set((state) => ({
|
|
pendingOperations: { ...state.pendingOperations, [operation.id]: operation },
|
|
error: null,
|
|
quoteBlockers: command.kind === 'request_quote' ? [] : state.quoteBlockers,
|
|
}));
|
|
try {
|
|
const result = await submitImageWorkspaceCommand(command);
|
|
set((state) => {
|
|
const pendingOperations = { ...state.pendingOperations };
|
|
delete pendingOperations[operation.id];
|
|
const fieldDrafts = { ...state.fieldDrafts };
|
|
for (const path of operation.clearDraftPaths) delete fieldDrafts[path];
|
|
const assistantStreams = { ...state.assistantStreams };
|
|
delete assistantStreams[operation.id];
|
|
return {
|
|
...withWorkspace(state, result.workspace),
|
|
pendingOperations,
|
|
fieldDrafts,
|
|
chatDraft: operation.clearChatDraft ? '' : state.chatDraft,
|
|
assistantStreams,
|
|
};
|
|
});
|
|
return result.workspace;
|
|
} catch (error) {
|
|
const message = messageForError(error);
|
|
if (isDirectionConflict(error)) {
|
|
set((state) => {
|
|
const pendingOperations = { ...state.pendingOperations };
|
|
delete pendingOperations[operation.id];
|
|
return { pendingOperations, error: '设计已在其他位置更新,已刷新最新内容,请再次提交' };
|
|
});
|
|
await get().refreshWorkspace().catch(() => null);
|
|
} else if (isAuthError(error)) {
|
|
set((state) => {
|
|
const pendingOperations = { ...state.pendingOperations };
|
|
delete pendingOperations[operation.id];
|
|
return { pendingOperations, status: 'auth-required', error: message };
|
|
});
|
|
} else {
|
|
set((state) => ({
|
|
pendingOperations: {
|
|
...state.pendingOperations,
|
|
[operation.id]: { ...operation, status: 'unknown', error: message },
|
|
},
|
|
error: '操作结果尚未确认,可使用同一操作标识安全重试',
|
|
}));
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
return {
|
|
status: 'idle',
|
|
bootstrap: null,
|
|
activeWorkspaceId: null,
|
|
workspace: null,
|
|
deletingWorkspaceId: null,
|
|
eventState: 'idle',
|
|
lastEventId: null,
|
|
pendingOperations: {},
|
|
fieldDrafts: {},
|
|
chatDraft: '',
|
|
assistantStreams: {},
|
|
quoteBlockers: [],
|
|
error: null,
|
|
|
|
load: async () => {
|
|
if (inFlightLoad) return inFlightLoad;
|
|
inFlightLoad = (async () => {
|
|
if (!useAuthStore.getState().isAuthenticated()) {
|
|
set({ status: 'auth-required', error: null });
|
|
return null;
|
|
}
|
|
set({ status: 'loading', error: null });
|
|
try {
|
|
const bootstrap = await fetchImageWorkspace();
|
|
const currentId = get().activeWorkspaceId;
|
|
const selectedId = currentId && bootstrap.workspaces.some(
|
|
(item) => item.workspaceId === currentId,
|
|
)
|
|
? currentId
|
|
: bootstrap.workspaces[0]?.workspaceId ?? null;
|
|
set({
|
|
status: 'ready',
|
|
bootstrap,
|
|
activeWorkspaceId: selectedId,
|
|
workspace: selectedId === currentId ? get().workspace : null,
|
|
error: null,
|
|
});
|
|
if (selectedId) {
|
|
await refreshWorkspaceById(selectedId);
|
|
get().connectEvents();
|
|
} else {
|
|
closeEventSource();
|
|
}
|
|
return bootstrap;
|
|
} catch (error) {
|
|
set({
|
|
status: isAuthError(error)
|
|
? 'auth-required'
|
|
: isUnavailableError(error)
|
|
? 'unavailable'
|
|
: 'error',
|
|
error: messageForError(error),
|
|
});
|
|
return null;
|
|
} finally {
|
|
inFlightLoad = null;
|
|
}
|
|
})();
|
|
return inFlightLoad;
|
|
},
|
|
|
|
createProject: async (title) => {
|
|
const workspace = await createImageWorkspaceProject(title);
|
|
selectionGeneration += 1;
|
|
closeEventSource();
|
|
set((state) => ({
|
|
...withWorkspace(state, workspace),
|
|
fieldDrafts: {},
|
|
chatDraft: '',
|
|
quoteBlockers: [],
|
|
lastEventId: null,
|
|
}));
|
|
get().connectEvents();
|
|
return workspace;
|
|
},
|
|
|
|
renameProject: async (workspaceId, title) => {
|
|
const workspace = await renameImageWorkspaceProject(workspaceId, title);
|
|
set((state) => {
|
|
if (state.activeWorkspaceId === workspaceId) {
|
|
return withWorkspace(state, workspace);
|
|
}
|
|
return {
|
|
bootstrap: state.bootstrap
|
|
? {
|
|
...state.bootstrap,
|
|
workspaces: mergeSummary(state.bootstrap.workspaces, workspace.workspace),
|
|
}
|
|
: state.bootstrap,
|
|
error: null,
|
|
};
|
|
});
|
|
return workspace;
|
|
},
|
|
|
|
deleteProject: async (workspaceId) => {
|
|
set({ deletingWorkspaceId: workspaceId, error: null });
|
|
try {
|
|
const result = await deleteImageWorkspaceProject(workspaceId);
|
|
const remaining = get().bootstrap?.workspaces.filter(
|
|
(item) => item.workspaceId !== workspaceId,
|
|
) ?? [];
|
|
const deletingActive = get().activeWorkspaceId === workspaceId;
|
|
if (deletingActive) {
|
|
selectionGeneration += 1;
|
|
closeEventSource();
|
|
}
|
|
set((state) => ({
|
|
bootstrap: state.bootstrap ? { ...state.bootstrap, workspaces: remaining } : null,
|
|
activeWorkspaceId: deletingActive ? remaining[0]?.workspaceId ?? null : state.activeWorkspaceId,
|
|
workspace: deletingActive ? null : state.workspace,
|
|
deletingWorkspaceId: null,
|
|
fieldDrafts: deletingActive ? {} : state.fieldDrafts,
|
|
chatDraft: deletingActive ? '' : state.chatDraft,
|
|
error: null,
|
|
}));
|
|
if (deletingActive && remaining[0]) {
|
|
await refreshWorkspaceById(remaining[0].workspaceId);
|
|
get().connectEvents();
|
|
}
|
|
return result;
|
|
} catch (error) {
|
|
set({ deletingWorkspaceId: null, error: messageForError(error) });
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
selectProject: async (workspaceId) => {
|
|
if (workspaceId === get().activeWorkspaceId && get().workspace) return;
|
|
selectionGeneration += 1;
|
|
closeEventSource();
|
|
set({
|
|
activeWorkspaceId: workspaceId,
|
|
workspace: null,
|
|
eventState: 'connecting',
|
|
lastEventId: null,
|
|
fieldDrafts: {},
|
|
chatDraft: '',
|
|
quoteBlockers: [],
|
|
error: null,
|
|
});
|
|
try {
|
|
await refreshWorkspaceById(workspaceId);
|
|
get().connectEvents();
|
|
} catch (error) {
|
|
set({ error: messageForError(error), eventState: 'degraded' });
|
|
}
|
|
},
|
|
|
|
refreshWorkspace: async () => {
|
|
const workspaceId = get().activeWorkspaceId;
|
|
if (!workspaceId) return null;
|
|
return refreshWorkspaceById(workspaceId);
|
|
},
|
|
|
|
connectEvents: () => {
|
|
const workspace = get().workspace;
|
|
if (!workspace) return;
|
|
const workspaceId = workspace.workspace.workspaceId;
|
|
if (activeEventSource && activeEventWorkspaceId === workspaceId) return;
|
|
closeEventSource();
|
|
activeEventWorkspaceId = workspaceId;
|
|
set({ eventState: 'connecting' });
|
|
void openImageWorkspaceEvents(
|
|
workspaceId,
|
|
workspace.workspace.sessionId,
|
|
get().lastEventId ?? undefined,
|
|
).then((source) => {
|
|
if (activeEventWorkspaceId !== workspaceId || get().activeWorkspaceId !== workspaceId) {
|
|
source.close();
|
|
return;
|
|
}
|
|
activeEventSource = source;
|
|
source.onopen = () => set({ eventState: 'connected' });
|
|
source.onerror = () => {
|
|
set({ eventState: 'degraded' });
|
|
void get().refreshWorkspace().catch(() => null);
|
|
};
|
|
const receive = (raw: Event) => {
|
|
const event = parseWorkspaceEvent(raw);
|
|
if (!event || get().activeWorkspaceId !== workspaceId) return;
|
|
if (event.type === 'design.assistant.delta') {
|
|
set((state) => ({
|
|
lastEventId: event.id,
|
|
assistantStreams: {
|
|
...state.assistantStreams,
|
|
[event.clientOperationId]: `${state.assistantStreams[event.clientOperationId] ?? ''}${event.delta}`,
|
|
},
|
|
}));
|
|
return;
|
|
}
|
|
set((state) => {
|
|
if (!state.workspace) return { lastEventId: event.id };
|
|
const nextWorkspace = applyEventToWorkspace(state.workspace, event);
|
|
updateBackgroundLease(nextWorkspace.tasks);
|
|
return {
|
|
workspace: nextWorkspace,
|
|
bootstrap: state.bootstrap
|
|
? {
|
|
...state.bootstrap,
|
|
workspaces: mergeSummary(state.bootstrap.workspaces, nextWorkspace.workspace),
|
|
}
|
|
: null,
|
|
lastEventId: event.id,
|
|
quoteBlockers: event.type === 'design.quote.blocked'
|
|
? event.blockers
|
|
: state.quoteBlockers,
|
|
};
|
|
});
|
|
if (event.type === 'design.workspace.updated' && event.changedDirection) {
|
|
void get().refreshWorkspace().catch(() => null);
|
|
}
|
|
};
|
|
for (const eventType of [
|
|
'design.session.snapshot',
|
|
'design.assistant.delta',
|
|
'design.direction.updated',
|
|
'design.quote.blocked',
|
|
'design.workspace.updated',
|
|
]) source.addEventListener(eventType, receive);
|
|
}).catch((error) => {
|
|
if (activeEventWorkspaceId === workspaceId) {
|
|
set({ eventState: 'degraded', error: messageForError(error) });
|
|
}
|
|
});
|
|
},
|
|
|
|
disconnectEvents: () => {
|
|
closeEventSource();
|
|
set({ eventState: 'idle' });
|
|
},
|
|
|
|
setFieldDraft: (path, value) => set((state) => ({
|
|
fieldDrafts: { ...state.fieldDrafts, [path]: value },
|
|
})),
|
|
|
|
discardFieldDraft: (path) => set((state) => {
|
|
const fieldDrafts = { ...state.fieldDrafts };
|
|
delete fieldDrafts[path];
|
|
return { fieldDrafts };
|
|
}),
|
|
|
|
setChatDraft: (value) => set({ chatDraft: value }),
|
|
|
|
applyFieldOperations: async (operations, clearDraftPaths = []) => {
|
|
const workspace = get().workspace;
|
|
if (!workspace) throw new Error('请先选择一个设计项目');
|
|
const command: DesignCommandInput = {
|
|
kind: 'apply_input',
|
|
workspaceId: workspace.workspace.workspaceId,
|
|
sessionId: workspace.workspace.sessionId,
|
|
expectedDirectionRevision: workspace.form.directionRevision,
|
|
clientOperationId: createImageWorkspaceOperationId(),
|
|
input: { kind: 'direct_edit', operations },
|
|
};
|
|
return executeCommand(command, {
|
|
label: '保存设计字段',
|
|
clearDraftPaths,
|
|
});
|
|
},
|
|
|
|
sendChat: async () => {
|
|
const workspace = get().workspace;
|
|
const message = get().chatDraft.trim();
|
|
if (!workspace) throw new Error('请先选择一个设计项目');
|
|
if (!message) throw new Error('请输入想法或修改要求');
|
|
const command: DesignCommandInput = {
|
|
kind: 'apply_input',
|
|
workspaceId: workspace.workspace.workspaceId,
|
|
sessionId: workspace.workspace.sessionId,
|
|
expectedDirectionRevision: workspace.form.directionRevision,
|
|
clientOperationId: createImageWorkspaceOperationId(),
|
|
input: { kind: 'chat', message },
|
|
};
|
|
return executeCommand(command, { label: '发送设计对话', clearChatDraft: true });
|
|
},
|
|
|
|
resolveDecisionPrompt: async (promptId, action, optionId) => {
|
|
const workspace = get().workspace;
|
|
if (!workspace) throw new Error('请先选择一个设计项目');
|
|
const command: DesignCommandInput = {
|
|
kind: 'apply_input',
|
|
workspaceId: workspace.workspace.workspaceId,
|
|
sessionId: workspace.workspace.sessionId,
|
|
expectedDirectionRevision: workspace.form.directionRevision,
|
|
clientOperationId: createImageWorkspaceOperationId(),
|
|
input: {
|
|
kind: 'prompt_resolution',
|
|
promptId,
|
|
action,
|
|
...(optionId ? { optionId } : {}),
|
|
},
|
|
};
|
|
return executeCommand(command, { label: '确认设计决策' });
|
|
},
|
|
|
|
requestQuote: async () => {
|
|
const workspace = get().workspace;
|
|
if (!workspace) throw new Error('请先选择一个设计项目');
|
|
const command: DesignCommandInput = {
|
|
kind: 'request_quote',
|
|
workspaceId: workspace.workspace.workspaceId,
|
|
sessionId: workspace.workspace.sessionId,
|
|
expectedDirectionRevision: workspace.form.directionRevision,
|
|
specificationRevision: workspace.form.specificationRevision,
|
|
clientOperationId: createImageWorkspaceOperationId(),
|
|
};
|
|
return executeCommand(command, { label: '编译生成方案' });
|
|
},
|
|
|
|
confirmGeneration: async (quoteId) => {
|
|
const workspace = get().workspace;
|
|
if (!workspace) throw new Error('请先选择一个设计项目');
|
|
const command: DesignCommandInput = {
|
|
kind: 'confirm_generation',
|
|
workspaceId: workspace.workspace.workspaceId,
|
|
sessionId: workspace.workspace.sessionId,
|
|
quoteId,
|
|
expectedDirectionRevision: workspace.form.directionRevision,
|
|
clientOperationId: createImageWorkspaceOperationId(),
|
|
};
|
|
return executeCommand(command, { label: '确认生成' });
|
|
},
|
|
|
|
retryOperation: async (clientOperationId) => {
|
|
const pending = get().pendingOperations[clientOperationId];
|
|
if (!pending) throw new Error('待重试操作不存在');
|
|
return executeCommand(pending.command, {
|
|
label: pending.label,
|
|
clearDraftPaths: pending.clearDraftPaths,
|
|
clearChatDraft: pending.clearChatDraft,
|
|
});
|
|
},
|
|
|
|
reset: () => {
|
|
selectionGeneration += 1;
|
|
closeEventSource();
|
|
updateBackgroundLease([]);
|
|
set({
|
|
status: 'idle',
|
|
bootstrap: null,
|
|
activeWorkspaceId: null,
|
|
workspace: null,
|
|
deletingWorkspaceId: null,
|
|
eventState: 'idle',
|
|
lastEventId: null,
|
|
pendingOperations: {},
|
|
fieldDrafts: {},
|
|
chatDraft: '',
|
|
assistantStreams: {},
|
|
quoteBlockers: [],
|
|
error: null,
|
|
});
|
|
},
|
|
};
|
|
});
|
|
|
|
export function recentDesignChanges(workspace: DesignWorkspace | null): DesignFieldChange[] {
|
|
return workspace?.form.recentChangeSet.changes ?? [];
|
|
}
|