"use client"; import { create } from "zustand"; import type { Workspace, MemberWithUser, Agent, Skill } from "@multica/types"; import { useIssueStore } from "@/features/issues"; import { useInboxStore } from "@/features/inbox"; import { api } from "@/shared/api"; import { createLogger } from "@/shared/logger"; const logger = createLogger("workspace-store"); interface WorkspaceState { workspace: Workspace | null; workspaces: Workspace[]; members: MemberWithUser[]; agents: Agent[]; skills: Skill[]; } interface WorkspaceActions { hydrateWorkspace: ( wsList: Workspace[], preferredWorkspaceId?: string | null, ) => Promise; switchWorkspace: (workspaceId: string) => Promise; refreshWorkspaces: () => Promise; refreshMembers: () => Promise; updateAgent: (id: string, updates: Partial) => void; refreshAgents: () => Promise; refreshSkills: () => Promise; upsertSkill: (skill: Skill) => void; removeSkill: (id: string) => void; createWorkspace: (data: { name: string; slug: string; description?: string; }) => Promise; updateWorkspace: (ws: Workspace) => void; leaveWorkspace: (workspaceId: string) => Promise; deleteWorkspace: (workspaceId: string) => Promise; clearWorkspace: () => void; } type WorkspaceStore = WorkspaceState & WorkspaceActions; export const useWorkspaceStore = create((set, get) => ({ // State workspace: null, workspaces: [], members: [], agents: [], skills: [], // Actions hydrateWorkspace: async (wsList, preferredWorkspaceId) => { set({ workspaces: wsList }); const nextWorkspace = (preferredWorkspaceId ? wsList.find((item) => item.id === preferredWorkspaceId) : null) ?? wsList[0] ?? null; if (!nextWorkspace) { api.setWorkspaceId(null); localStorage.removeItem("multica_workspace_id"); set({ workspace: null, members: [], agents: [], skills: [] }); return null; } api.setWorkspaceId(nextWorkspace.id); localStorage.setItem("multica_workspace_id", nextWorkspace.id); set({ workspace: nextWorkspace }); logger.debug("hydrate workspace", nextWorkspace.name, nextWorkspace.id); const [nextMembers, nextAgents, nextSkills] = await Promise.all([ api.listMembers(nextWorkspace.id), api.listAgents({ workspace_id: nextWorkspace.id }), api.listSkills().catch(() => [] as Skill[]), useIssueStore.getState().fetch(), useInboxStore.getState().fetch(), ]); logger.info("hydrate complete", "members:", nextMembers.length, "agents:", nextAgents.length); set({ members: nextMembers, agents: nextAgents, skills: nextSkills }); return nextWorkspace; }, switchWorkspace: async (workspaceId) => { logger.info("switching to", workspaceId); const { workspaces, hydrateWorkspace } = get(); const ws = workspaces.find((item) => item.id === workspaceId); if (!ws) return; // Clear stale data from other stores before switching useIssueStore.getState().setIssues([]); useInboxStore.getState().setItems([]); set({ agents: [], skills: [] }); await hydrateWorkspace(workspaces, ws.id); }, refreshWorkspaces: async () => { const { workspace, hydrateWorkspace } = get(); const storedWorkspaceId = localStorage.getItem("multica_workspace_id"); const wsList = await api.listWorkspaces(); await hydrateWorkspace(wsList, workspace?.id ?? storedWorkspaceId); return wsList; }, refreshMembers: async () => { const { workspace } = get(); if (!workspace) return; const members = await api.listMembers(workspace.id); set({ members }); }, updateAgent: (id, updates) => set((s) => ({ agents: s.agents.map((a) => (a.id === id ? { ...a, ...updates } : a)), })), refreshAgents: async () => { const { workspace } = get(); if (!workspace) return; const agents = await api.listAgents({ workspace_id: workspace.id }); set({ agents }); }, refreshSkills: async () => { const { workspace, skills: existing } = get(); if (!workspace) return; const fetched = await api.listSkills(); // listSkills doesn't include files — preserve files from existing entries const filesById = new Map( existing.filter((s) => s.files?.length).map((s) => [s.id, s.files]), ); const merged = fetched.map((s) => ({ ...s, files: s.files ?? filesById.get(s.id) ?? [], })); set({ skills: merged }); }, upsertSkill: (skill) => { set((state) => { const idx = state.skills.findIndex((s) => s.id === skill.id); if (idx >= 0) { const next = [...state.skills]; next[idx] = skill; return { skills: next }; } return { skills: [...state.skills, skill] }; }); }, removeSkill: (id) => { set((state) => ({ skills: state.skills.filter((s) => s.id !== id) })); }, createWorkspace: async (data) => { const ws = await api.createWorkspace(data); set((state) => ({ workspaces: [...state.workspaces, ws] })); return ws; }, updateWorkspace: (ws) => { set((state) => ({ workspace: state.workspace?.id === ws.id ? ws : state.workspace, workspaces: state.workspaces.map((item) => item.id === ws.id ? ws : item, ), })); }, leaveWorkspace: async (workspaceId) => { await api.leaveWorkspace(workspaceId); const { workspace, hydrateWorkspace } = get(); const wsList = await api.listWorkspaces(); const preferredWorkspaceId = workspace?.id === workspaceId ? null : (workspace?.id ?? null); await hydrateWorkspace(wsList, preferredWorkspaceId); }, deleteWorkspace: async (workspaceId) => { await api.deleteWorkspace(workspaceId); const { workspace, hydrateWorkspace } = get(); const wsList = await api.listWorkspaces(); const preferredWorkspaceId = workspace?.id === workspaceId ? null : (workspace?.id ?? null); await hydrateWorkspace(wsList, preferredWorkspaceId); }, clearWorkspace: () => { api.setWorkspaceId(null); localStorage.removeItem("multica_workspace_id"); set({ workspace: null, workspaces: [], members: [], agents: [], skills: [] }); }, }));