import { create } from 'zustand'; import { invoke } from '@tauri-apps/api/core'; import type { GatewayClient } from '../lib/gateway-client'; import type { KernelClient } from '../lib/kernel-client'; // === Core Types (previously imported from gatewayStore) === export interface Workflow { id: string; name: string; steps: number; description?: string; createdAt?: string; } export interface WorkflowRun { runId: string; status: string; step?: string; result?: unknown; } // === Types === interface RawWorkflowRun { runId?: string; run_id?: string; id?: string; workflowId?: string; workflow_id?: string; status?: string; startedAt?: string; started_at?: string; completedAt?: string; completed_at?: string; currentStep?: number; current_step?: number; totalSteps?: number; total_steps?: number; error?: string; result?: unknown; step?: string; } export interface WorkflowStep { handName: string; name?: string; params?: Record; condition?: string; } export interface WorkflowDetail { id: string; name: string; description?: string; steps: WorkflowStep[]; createdAt?: string; } export interface WorkflowCreateOptions { name: string; description?: string; steps: WorkflowStep[]; } export interface UpdateWorkflowInput { name?: string; description?: string; steps?: WorkflowStep[]; } // Extended WorkflowRun with additional fields from API export interface ExtendedWorkflowRun extends WorkflowRun { startedAt?: string; completedAt?: string; error?: string; } // === Client Interface === interface WorkflowClient { listWorkflows(): Promise<{ workflows: { id: string; name: string; steps: number; description?: string; createdAt?: string }[] } | null>; getWorkflow(id: string): Promise; createWorkflow(workflow: WorkflowCreateOptions): Promise<{ id: string; name: string } | null>; updateWorkflow(id: string, updates: UpdateWorkflowInput): Promise<{ id: string; name: string } | null>; deleteWorkflow(id: string): Promise<{ status: string }>; executeWorkflow(id: string, input?: Record): Promise<{ runId: string; status: string } | null>; cancelWorkflow(workflowId: string, runId: string): Promise<{ status: string }>; listWorkflowRuns(workflowId: string, opts?: { limit?: number; offset?: number }): Promise<{ runs: RawWorkflowRun[] } | null>; } // === Store State Slice === export interface WorkflowStateSlice { workflows: Workflow[]; workflowRuns: Record; isLoading: boolean; error: string | null; client: WorkflowClient; } // === Store Actions Slice === export interface WorkflowActionsSlice { setWorkflowStoreClient: (client: WorkflowClient) => void; loadWorkflows: () => Promise; getWorkflow: (id: string) => Workflow | undefined; getWorkflowDetail: (id: string) => Promise; createWorkflow: (workflow: WorkflowCreateOptions) => Promise; updateWorkflow: (id: string, updates: UpdateWorkflowInput) => Promise; deleteWorkflow: (id: string) => Promise; triggerWorkflow: (id: string, input?: Record) => Promise<{ runId: string; status: string } | undefined>; cancelWorkflow: (id: string, runId: string) => Promise; loadWorkflowRuns: (workflowId: string, opts?: { limit?: number; offset?: number }) => Promise; clearError: () => void; reset: () => void; } // === Combined Store Type === export type WorkflowStore = WorkflowStateSlice & WorkflowActionsSlice; // === Initial State === const initialState = { workflows: [], workflowRuns: {}, isLoading: false, error: null, client: null as unknown as WorkflowClient, }; // === Store === export const useWorkflowStore = create((set, get) => ({ ...initialState, setWorkflowStoreClient: (client: WorkflowClient) => { set({ client }); }, loadWorkflows: async () => { set({ isLoading: true, error: null }); try { const result = await get().client.listWorkflows(); const workflows: Workflow[] = (result?.workflows || []).map(w => ({ id: w.id, name: w.name, steps: w.steps, description: w.description, createdAt: w.createdAt, })); set({ workflows, isLoading: false }); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to load workflows'; set({ error: message, isLoading: false }); } }, getWorkflow: (id: string) => { return get().workflows.find(w => w.id === id); }, getWorkflowDetail: async (id: string) => { try { const result = await get().client.getWorkflow(id); if (!result) return undefined; return { id: result.id, name: result.name, description: result.description, steps: Array.isArray(result.steps) ? result.steps : [], createdAt: result.createdAt, }; } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to load workflow details'; set({ error: message }); return undefined; } }, createWorkflow: async (workflow: WorkflowCreateOptions) => { set({ error: null }); try { const result = await get().client.createWorkflow(workflow); if (result) { const newWorkflow: Workflow = { id: result.id, name: result.name, steps: workflow.steps.length, description: workflow.description, }; set(state => ({ workflows: [...state.workflows, newWorkflow] })); return newWorkflow; } return undefined; } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to create workflow'; set({ error: message }); return undefined; } }, updateWorkflow: async (id: string, updates: UpdateWorkflowInput) => { set({ error: null }); try { const result = await get().client.updateWorkflow(id, updates); if (result) { set(state => ({ workflows: state.workflows.map(w => w.id === id ? { ...w, name: updates.name ?? w.name, description: updates.description ?? w.description, steps: updates.steps?.length ?? w.steps, } : w ), })); return get().workflows.find(w => w.id === id); } return undefined; } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to update workflow'; set({ error: message }); return undefined; } }, deleteWorkflow: async (id: string) => { set({ error: null }); try { await get().client.deleteWorkflow(id); set(state => ({ workflows: state.workflows.filter(w => w.id !== id), workflowRuns: (() => { const { [id]: _, ...rest } = state.workflowRuns; return rest; })(), })); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to delete workflow'; set({ error: message }); throw err; } }, triggerWorkflow: async (id: string, input?: Record) => { set({ error: null }); try { const result = await get().client.executeWorkflow(id, input); return result ? { runId: result.runId, status: result.status } : undefined; } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to trigger workflow'; set({ error: message }); return undefined; } }, cancelWorkflow: async (id: string, runId: string) => { set({ error: null }); try { await get().client.cancelWorkflow(id, runId); // Refresh workflows to update status await get().loadWorkflows(); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to cancel workflow'; set({ error: message }); throw err; } }, loadWorkflowRuns: async (workflowId: string, opts?: { limit?: number; offset?: number }) => { try { const result = await get().client.listWorkflowRuns(workflowId, opts); const runs: ExtendedWorkflowRun[] = (result?.runs || []).map((r: RawWorkflowRun) => ({ runId: r.runId || r.run_id || r.id || '', status: r.status || 'unknown', startedAt: r.startedAt || r.started_at, completedAt: r.completedAt || r.completed_at, step: r.currentStep?.toString() || r.current_step?.toString() || r.step, result: r.result, error: r.error, })); // Store runs by workflow ID set(state => ({ workflowRuns: { ...state.workflowRuns, [workflowId]: runs }, })); return runs; } catch { return []; } }, clearError: () => { set({ error: null }); }, reset: () => { set(initialState); }, })); // Types are now defined locally in this file (no longer imported from gatewayStore) // === Client Injection === /** * Helper to create a WorkflowClient adapter from a GatewayClient. */ function createWorkflowClientFromGateway(client: GatewayClient): WorkflowClient { return { getWorkflow: async (id: string) => { const result = await client.getWorkflow(id); if (!result) return null; return { ...result, steps: result.steps as WorkflowStep[], }; }, listWorkflows: () => client.listWorkflows(), createWorkflow: (workflow) => client.createWorkflow(workflow), updateWorkflow: (id, updates) => client.updateWorkflow(id, updates), deleteWorkflow: (id) => client.deleteWorkflow(id), executeWorkflow: (id, input) => client.executeWorkflow(id, input), cancelWorkflow: (workflowId, runId) => client.cancelWorkflow(workflowId, runId), listWorkflowRuns: (workflowId, opts) => client.listWorkflowRuns(workflowId, opts), }; } // === Pipeline types (from Tauri backend) === interface PipelineInfo { id: string; displayName: string; description: string; category: string; industry: string; tags: string[]; icon: string; version: string; author: string; inputs: Array<{ name: string; inputType: string; required: boolean; label: string; placeholder?: string; default?: unknown; options: string[]; }>; } interface RunPipelineResponse { runId: string; pipelineId: string; status: string; } interface PipelineRunResponse { runId: string; pipelineId: string; status: string; currentStep?: string; percentage: number; message: string; outputs?: unknown; error?: string; startedAt: string; endedAt?: string; } /** * Helper to create a WorkflowClient adapter from a KernelClient. * Uses direct Tauri invoke() calls to pipeline_commands since KernelClient * does not have workflow methods (workflows in Tauri mode are pipelines). */ function createWorkflowClientFromKernel(_client: KernelClient): WorkflowClient { return { listWorkflows: async () => { try { const pipelines = await invoke('pipeline_list', {}); if (!pipelines) return null; return { workflows: pipelines.map((p) => ({ id: p.id, name: p.displayName || p.id, steps: p.inputs.length, description: p.description, createdAt: undefined, })), }; } catch { return null; } }, getWorkflow: async (id: string) => { try { const pipeline = await invoke('pipeline_get', { pipelineId: id }); return { id: pipeline.id, name: pipeline.displayName || pipeline.id, description: pipeline.description, steps: pipeline.inputs.map((input) => ({ handName: input.inputType, name: input.label, params: input.default ? { default: input.default } : undefined, })), createdAt: undefined, } satisfies WorkflowDetail; } catch { return null; } }, createWorkflow: async (workflow) => { try { const result = await invoke<{ id: string; name: string }>('pipeline_create', { request: { name: workflow.name, description: workflow.description, steps: workflow.steps.map((s, i) => ({ handName: s.handName, name: s.name || `Step ${i + 1}`, params: s.params, condition: s.condition, })), }, }); return result; } catch { return null; } }, updateWorkflow: async (id, updates) => { try { const result = await invoke<{ id: string; name: string }>('pipeline_update', { pipelineId: id, request: { name: updates.name, description: updates.description, steps: updates.steps?.map((s, i) => ({ handName: s.handName, name: s.name || `Step ${i + 1}`, params: s.params, condition: s.condition, })), }, }); return result; } catch { return null; } }, deleteWorkflow: async (id) => { try { await invoke('pipeline_delete', { pipelineId: id }); return { status: 'deleted' }; } catch { return { status: 'error' }; } }, executeWorkflow: async (id: string, input?: Record) => { try { const result = await invoke('pipeline_run', { request: { pipelineId: id, inputs: input || {} }, }); return { runId: result.runId, status: result.status }; } catch { return null; } }, cancelWorkflow: async (_workflowId: string, runId: string) => { try { await invoke('pipeline_cancel', { runId }); return { status: 'cancelled' }; } catch { return { status: 'error' }; } }, listWorkflowRuns: async (workflowId: string) => { try { const runs = await invoke('pipeline_runs', {}); // Filter runs by pipeline ID and map to RawWorkflowRun shape const filteredRuns: RawWorkflowRun[] = runs .filter((r) => r.pipelineId === workflowId) .map((r) => ({ run_id: r.runId, workflow_id: r.pipelineId, status: r.status, started_at: r.startedAt, completed_at: r.endedAt, current_step: r.currentStep ? Math.round(r.percentage) : undefined, error: r.error, result: r.outputs, })); return { runs: filteredRuns }; } catch { return { runs: [] }; } }, }; } /** * Sets the client for the workflow store. * Called by the coordinator during initialization. * Detects whether the client is a KernelClient (Tauri) or GatewayClient (browser). */ export function setWorkflowStoreClient(client: unknown): void { let workflowClient: WorkflowClient; // Check if it's a KernelClient (has listHands method, which KernelClient has but GatewayClient doesn't) if (client && typeof client === 'object' && 'listHands' in client) { workflowClient = createWorkflowClientFromKernel(client as KernelClient); } else if (client && typeof client === 'object') { // It's a GatewayClient workflowClient = createWorkflowClientFromGateway(client as GatewayClient); } else { // Fallback: return a stub client that gracefully handles all calls workflowClient = { listWorkflows: async () => null, getWorkflow: async () => null, createWorkflow: async () => null, updateWorkflow: async () => null, deleteWorkflow: async () => ({ status: 'error' }), executeWorkflow: async () => null, cancelWorkflow: async () => ({ status: 'error' }), listWorkflowRuns: async () => null, }; } useWorkflowStore.getState().setWorkflowStoreClient(workflowClient); }