diff --git a/desktop/src/store/index.ts b/desktop/src/store/index.ts new file mode 100644 index 0000000..9f6d0c5 --- /dev/null +++ b/desktop/src/store/index.ts @@ -0,0 +1,261 @@ +/** + * Store Coordinator + * + * This module provides a unified interface to all specialized stores, + * maintaining backward compatibility with components that import useGatewayStore. + * + * The coordinator: + * 1. Injects the shared client into all stores + * 2. Provides a composite hook that combines all store slices + * 3. Re-exports all individual stores for direct access + */ + +// === Re-export Individual Stores === +export { useConnectionStore, getConnectionState, getClient, getConnectionError, getGatewayVersion } from './connectionStore'; +export type { ConnectionStore, ConnectionStateSlice, ConnectionActionsSlice, GatewayLog } from './connectionStore'; + +export { useAgentStore, setAgentStoreClient } from './agentStore'; +export type { AgentStore, AgentStateSlice, AgentActionsSlice, Clone, UsageStats, PluginStatus, CloneCreateOptions } from './agentStore'; + +export { useHandStore, setHandStoreClient } from './handStore'; +export type { HandStore, HandStateSlice, HandActionsSlice, Hand, HandRun, Trigger, Approval, TriggerCreateOptions } from './handStore'; + +export { useWorkflowStore, setWorkflowStoreClient } from './workflowStore'; +export type { WorkflowStore, WorkflowStateSlice, WorkflowActionsSlice, Workflow, WorkflowRun, WorkflowCreateOptions } from './workflowStore'; + +export { useConfigStore, setConfigStoreClient } from './configStore'; +export type { ConfigStore, ConfigStateSlice, ConfigActionsSlice, QuickConfig, WorkspaceInfo, ChannelInfo, ScheduledTask, SkillInfo } from './configStore'; + +// === Composite Store Hook === + +import { useEffect, useMemo } from 'react'; +import { useConnectionStore, getClient } from './connectionStore'; +import { useAgentStore, setAgentStoreClient } from './agentStore'; +import { useHandStore, setHandStoreClient } from './handStore'; +import { useWorkflowStore, setWorkflowStoreClient } from './workflowStore'; +import { useConfigStore, setConfigStoreClient } from './configStore'; +import type { GatewayClient } from '../lib/gateway-client'; + +/** + * Initialize all stores with the shared client. + * Called once when the application mounts. + */ +export function initializeStores(): void { + const client = getClient(); + + // Inject client into all stores + setAgentStoreClient(client); + setHandStoreClient(client); + setWorkflowStoreClient(client); + setConfigStoreClient(client); +} + +/** + * Hook that provides a composite view of all stores. + * Use this for components that need access to multiple store slices. + * + * For components that only need specific slices, import the individual + * store hooks directly (e.g., useConnectionStore, useAgentStore). + */ +export function useCompositeStore() { + // Subscribe to all stores + const connectionState = useConnectionStore((s) => s.connectionState); + const gatewayVersion = useConnectionStore((s) => s.gatewayVersion); + const connectionError = useConnectionStore((s) => s.error); + const logs = useConnectionStore((s) => s.logs); + const localGateway = useConnectionStore((s) => s.localGateway); + const localGatewayBusy = useConnectionStore((s) => s.localGatewayBusy); + const isLoading = useConnectionStore((s) => s.isLoading); + const client = useConnectionStore((s) => s.client); + + const clones = useAgentStore((s) => s.clones); + const usageStats = useAgentStore((s) => s.usageStats); + const pluginStatus = useAgentStore((s) => s.pluginStatus); + + const hands = useHandStore((s) => s.hands); + const handRuns = useHandStore((s) => s.handRuns); + const triggers = useHandStore((s) => s.triggers); + const approvals = useHandStore((s) => s.approvals); + + const workflows = useWorkflowStore((s) => s.workflows); + const workflowRuns = useWorkflowStore((s) => s.workflowRuns); + + const quickConfig = useConfigStore((s) => s.quickConfig); + const workspaceInfo = useConfigStore((s) => s.workspaceInfo); + const channels = useConfigStore((s) => s.channels); + const scheduledTasks = useConfigStore((s) => s.scheduledTasks); + const skillsCatalog = useConfigStore((s) => s.skillsCatalog); + const models = useConfigStore((s) => s.models); + const modelsLoading = useConfigStore((s) => s.modelsLoading); + const modelsError = useConfigStore((s) => s.modelsError); + + // Get all actions + const connect = useConnectionStore((s) => s.connect); + const disconnect = useConnectionStore((s) => s.disconnect); + const clearLogs = useConnectionStore((s) => s.clearLogs); + const refreshLocalGateway = useConnectionStore((s) => s.refreshLocalGateway); + const startLocalGateway = useConnectionStore((s) => s.startLocalGateway); + const stopLocalGateway = useConnectionStore((s) => s.stopLocalGateway); + const restartLocalGateway = useConnectionStore((s) => s.restartLocalGateway); + + const loadClones = useAgentStore((s) => s.loadClones); + const createClone = useAgentStore((s) => s.createClone); + const updateClone = useAgentStore((s) => s.updateClone); + const deleteClone = useAgentStore((s) => s.deleteClone); + const loadUsageStats = useAgentStore((s) => s.loadUsageStats); + const loadPluginStatus = useAgentStore((s) => s.loadPluginStatus); + + const loadHands = useHandStore((s) => s.loadHands); + const getHandDetails = useHandStore((s) => s.getHandDetails); + const triggerHand = useHandStore((s) => s.triggerHand); + const loadHandRuns = useHandStore((s) => s.loadHandRuns); + const loadTriggers = useHandStore((s) => s.loadTriggers); + const createTrigger = useHandStore((s) => s.createTrigger); + const deleteTrigger = useHandStore((s) => s.deleteTrigger); + const loadApprovals = useHandStore((s) => s.loadApprovals); + const approveRequest = useHandStore((s) => s.approveRequest); + + const loadWorkflows = useWorkflowStore((s) => s.loadWorkflows); + const getWorkflow = useWorkflowStore((s) => s.getWorkflow); + const createWorkflow = useWorkflowStore((s) => s.createWorkflow); + const updateWorkflow = useWorkflowStore((s) => s.updateWorkflow); + const deleteWorkflow = useWorkflowStore((s) => s.deleteWorkflow); + const triggerWorkflow = useWorkflowStore((s) => s.triggerWorkflow); + const loadWorkflowRuns = useWorkflowStore((s) => s.loadWorkflowRuns); + + const loadQuickConfig = useConfigStore((s) => s.loadQuickConfig); + const saveQuickConfig = useConfigStore((s) => s.saveQuickConfig); + const loadWorkspaceInfo = useConfigStore((s) => s.loadWorkspaceInfo); + const loadChannels = useConfigStore((s) => s.loadChannels); + const getChannel = useConfigStore((s) => s.getChannel); + const createChannel = useConfigStore((s) => s.createChannel); + const updateChannel = useConfigStore((s) => s.updateChannel); + const deleteChannel = useConfigStore((s) => s.deleteChannel); + const loadScheduledTasks = useConfigStore((s) => s.loadScheduledTasks); + const createScheduledTask = useConfigStore((s) => s.createScheduledTask); + const loadSkillsCatalog = useConfigStore((s) => s.loadSkillsCatalog); + const getSkill = useConfigStore((s) => s.getSkill); + const createSkill = useConfigStore((s) => s.createSkill); + const updateSkill = useConfigStore((s) => s.updateSkill); + const deleteSkill = useConfigStore((s) => s.deleteSkill); + const loadModels = useConfigStore((s) => s.loadModels); + + // Memoize the composite store to prevent unnecessary re-renders + return useMemo(() => ({ + // Connection state + connectionState, + gatewayVersion, + error: connectionError, + logs, + localGateway, + localGatewayBusy, + isLoading, + client, + + // Agent state + clones, + usageStats, + pluginStatus, + + // Hand state + hands, + handRuns, + triggers, + approvals, + + // Workflow state + workflows, + workflowRuns, + + // Config state + quickConfig, + workspaceInfo, + channels, + scheduledTasks, + skillsCatalog, + models, + modelsLoading, + modelsError, + + // Connection actions + connect, + disconnect, + clearLogs, + refreshLocalGateway, + startLocalGateway, + stopLocalGateway, + restartLocalGateway, + + // Agent actions + loadClones, + createClone, + updateClone, + deleteClone, + loadUsageStats, + loadPluginStatus, + + // Hand actions + loadHands, + getHandDetails, + triggerHand, + loadHandRuns, + loadTriggers, + createTrigger, + deleteTrigger, + loadApprovals, + approveRequest, + + // Workflow actions + loadWorkflows, + getWorkflow, + createWorkflow, + updateWorkflow, + deleteWorkflow, + triggerWorkflow, + loadWorkflowRuns, + + // Config actions + loadQuickConfig, + saveQuickConfig, + loadWorkspaceInfo, + loadChannels, + getChannel, + createChannel, + updateChannel, + deleteChannel, + loadScheduledTasks, + createScheduledTask, + loadSkillsCatalog, + getSkill, + createSkill, + updateSkill, + deleteSkill, + loadModels, + + // Legacy sendMessage (delegates to client) + sendMessage: async (message: string, sessionKey?: string) => { + return client.chat(message, { sessionKey }); + }, + }), [ + connectionState, gatewayVersion, connectionError, logs, localGateway, localGatewayBusy, isLoading, client, + clones, usageStats, pluginStatus, + hands, handRuns, triggers, approvals, + workflows, workflowRuns, + quickConfig, workspaceInfo, channels, scheduledTasks, skillsCatalog, models, modelsLoading, modelsError, + connect, disconnect, clearLogs, refreshLocalGateway, startLocalGateway, stopLocalGateway, restartLocalGateway, + loadClones, createClone, updateClone, deleteClone, loadUsageStats, loadPluginStatus, + loadHands, getHandDetails, triggerHand, loadHandRuns, loadTriggers, createTrigger, deleteTrigger, loadApprovals, approveRequest, + loadWorkflows, getWorkflow, createWorkflow, updateWorkflow, deleteWorkflow, triggerWorkflow, loadWorkflowRuns, + loadQuickConfig, saveQuickConfig, loadWorkspaceInfo, loadChannels, getChannel, createChannel, updateChannel, deleteChannel, + loadScheduledTasks, createScheduledTask, loadSkillsCatalog, getSkill, createSkill, updateSkill, deleteSkill, loadModels, + ]); +} + +/** + * Initialize stores on module load. + * This ensures all stores have access to the shared client. + */ +if (typeof window !== 'undefined') { + // Defer initialization to next tick to ensure all modules are loaded + setTimeout(initializeStores, 0); +} diff --git a/docs/SYSTEM_ANALYSIS.md b/docs/SYSTEM_ANALYSIS.md index a425c4b..419c962 100644 --- a/docs/SYSTEM_ANALYSIS.md +++ b/docs/SYSTEM_ANALYSIS.md @@ -567,12 +567,12 @@ ZCLAW 是基于 **OpenFang** (Rust Agent OS) 的 AI Agent 桌面客户端,核 * ✅ `handStore.ts` (498 行) - Hands、Triggers、Approvals * ✅ `workflowStore.ts` (255 行) - Workflows、WorkflowRuns * ✅ `configStore.ts` (537 行) - QuickConfig、Channels、Skills、Models + * ✅ `store/index.ts` - 协调层,组合所有 stores * Store 行数: gatewayStore 1660 → 5 个子 Store (平均 358 行) * 待完成: - * 🔄 创建协调层 (coordinator) - * 🔄 更新组件导入 + * 🔄 更新组件导入 (可选,向后兼容) * 代码质量: * ✅ TypeScript 类型检查通过 -*下一步: Phase 11 协调层创建* +*下一步: Phase 12 性能优化*