import { create } from "zustand"; import type { CanvasAgentOp, CanvasAgentSnapshot } from "@/lib/canvas/canvas-agent-ops"; export type AgentChatRole = "user" | "assistant" | "system" | "tool" | "error"; export type AgentAttachment = { id: string; name: string; type: string; size: number; width: number; height: number; url: string; dataUrl: string }; export type AgentChatItem = { id: string; role: AgentChatRole; title?: string; text: string; historyText?: string; meta?: string; detail?: unknown; attachments?: AgentAttachment[]; streamId?: string }; export type AgentEventLog = { id: string; time: string; title: string; text: string; raw?: unknown }; export type AgentPendingToolCall = { requestId: string; name: string; input?: { ops?: CanvasAgentOp[]; path?: string } & Record }; export type AgentCanvasContext = { snapshot: CanvasAgentSnapshot; applyOps: (ops?: CanvasAgentOp[]) => CanvasAgentSnapshot; undoOps: () => CanvasAgentSnapshot | null; canUndo: boolean }; export type AgentThreadSummary = { id: string; preview: string; name?: string | null; cwd?: string; status?: string; source?: unknown; createdAt?: number; updatedAt?: number }; export type AgentTokenUsage = { input: number; cached: number; output: number }; export type AgentPanelTab = "chat" | "setup" | "history" | "log"; const CONNECT_TIMEOUT_MS = 6000; let agentSource: EventSource | null = null; let connectTimer: ReturnType | null = null; type AgentStore = { width: number; panelOpen: boolean; panelMounted: boolean; panelClosing: boolean; canvasContext: AgentCanvasContext | null; url: string; token: string; connected: boolean; enabled: boolean; silentConnect: boolean; prompt: string; attachments: AgentAttachment[]; sending: boolean; waiting: boolean; messages: AgentChatItem[]; tokenUsage: AgentTokenUsage | null; eventLogs: AgentEventLog[]; threads: AgentThreadSummary[]; activeThreadId: string; workspacePath: string; loadingThreads: boolean; activeTab: AgentPanelTab; confirmTools: boolean; activity: string; connectError: string; pendingTool: AgentPendingToolCall | null; setAgentState: (patch: Partial>) => void; openPanel: () => void; closePanel: () => void; togglePanel: () => void; setCanvasContext: (context: AgentCanvasContext | null) => void; connectAgent: (options?: { silent?: boolean }) => void; disconnectAgent: (patch?: Partial>) => void; addMessage: (item: AgentChatItem) => void; addEventLog: (item: AgentEventLog) => void; clearEventLogs: () => void; }; export const CANVAS_AGENT_PANEL_MOTION_MS = 500; export const useAgentStore = create((set, get) => ({ width: typeof window === "undefined" ? 440 : Number(localStorage.getItem("canvas-agent-panel-width")) || 440, panelOpen: false, panelMounted: true, panelClosing: false, canvasContext: null, url: typeof window === "undefined" ? "http://127.0.0.1:17371" : localStorage.getItem("canvas-agent-url") || "http://127.0.0.1:17371", token: typeof window === "undefined" ? "" : localStorage.getItem("canvas-agent-token") || "", connected: false, enabled: false, silentConnect: false, prompt: "", attachments: [], sending: false, waiting: false, messages: [], tokenUsage: null, eventLogs: [], threads: [], activeThreadId: "", workspacePath: "", loadingThreads: false, activeTab: "setup", confirmTools: false, activity: "就绪", connectError: "", pendingTool: null, setAgentState: (patch) => set(patch), openPanel: () => set({ panelOpen: true, panelMounted: true, panelClosing: false }), closePanel: () => { if (!get().panelMounted || get().panelClosing) return; set({ panelOpen: false, panelClosing: true }); setTimeout(() => { if (get().panelClosing) set({ panelMounted: false, panelClosing: false }); }, CANVAS_AGENT_PANEL_MOTION_MS); }, togglePanel: () => (get().panelOpen ? get().closePanel() : get().openPanel()), setCanvasContext: (canvasContext) => set({ canvasContext }), connectAgent: (options) => { const silent = options?.silent ?? false; const endpoint = get().url.trim().replace(/\/$/, ""); const token = get().token.trim(); if (!endpoint || !token) return set({ connectError: silent ? "" : "请填写 Local URL 和 Connect token" }); try { const parsed = new URL(endpoint); if (!["http:", "https:"].includes(parsed.protocol)) throw new Error(); } catch { return set({ connectError: silent ? "" : "Local URL 格式不正确" }); } localStorage.setItem("canvas-agent-url", endpoint); localStorage.setItem("canvas-agent-token", token); // 只设 enabled=true,由 LocalAgentPanel 的 useEffect 统一负责开 SSE set({ url: endpoint, token, enabled: true, silentConnect: silent, activity: "连接中", connectError: "" }); }, disconnectAgent: (patch = {}) => { agentSource?.close(); agentSource = null; if (connectTimer) clearTimeout(connectTimer); connectTimer = null; set({ enabled: false, connected: false, silentConnect: false, activity: "离线", ...patch }); }, addMessage: (item) => set((state) => ({ messages: [...state.messages.slice(-120), item] })), addEventLog: (item) => set((state) => ({ eventLogs: [...state.eventLogs.slice(-160), item] })), clearEventLogs: () => set({ eventLogs: [] }), }));