import { create } from 'zustand'; export interface Project { id: string; name: string; description?: string; status: 'pending' | 'parsing' | 'ready' | 'error'; fps?: string; frames?: number; thumbnail?: string; thumbnail_url?: string; video_path?: string; source_type?: string; original_fps?: number; parse_fps?: number; createdAt?: string; updatedAt?: string; } export type AiModelId = 'sam2' | 'sam3'; export interface Frame { id: string; projectId: string; index: number; url: string; width: number; height: number; timestamp?: string; } export interface Annotation { id: string; frameId: string; type: 'polygon' | 'rectangle' | 'circle' | 'point' | 'mask'; points: number[]; label: string; color: string; zIndex?: number; confidence?: number; metadata?: Record; } export interface Mask { id: string; frameId: string; annotationId?: string; templateId?: string; classId?: string; className?: string; classZIndex?: number; saveStatus?: 'draft' | 'saved' | 'dirty' | 'saving' | 'error'; saved?: boolean; pathData: string; label: string; color: string; opacity?: number; segmentation?: number[][]; points?: number[][]; bbox?: [number, number, number, number]; area?: number; metadata?: Record; } export interface Template { id: string; name: string; description?: string; classes: TemplateClass[]; rules?: TemplateRule[]; createdAt?: string; updatedAt?: string; } export interface TemplateClass { id: string; name: string; color: string; zIndex: number; category?: string; description?: string; } export interface TemplateRule { id: string; name: string; sourceKey: string; targetKey: string; operation: string; } export interface AppState { // Auth isAuthenticated: boolean; token: string | null; login: (token: string) => void; logout: () => void; // Projects projects: Project[]; currentProject: Project | null; setProjects: (projects: Project[]) => void; setCurrentProject: (project: Project | null) => void; addProject: (project: Project) => void; updateProject: (project: Project) => void; // Workspace activeModule: string; activeTool: string; aiModel: AiModelId; frames: Frame[]; currentFrameIndex: number; annotations: Annotation[]; masks: Mask[]; maskHistory: Mask[][]; maskFuture: Mask[][]; setActiveModule: (module: string) => void; setActiveTool: (tool: string) => void; setAiModel: (model: AiModelId) => void; setFrames: (frames: Frame[]) => void; setCurrentFrame: (index: number) => void; addAnnotation: (annotation: Annotation) => void; addMask: (mask: Mask) => void; updateMask: (id: string, updates: Partial) => void; setMasks: (masks: Mask[]) => void; clearMasks: () => void; undoMasks: () => void; redoMasks: () => void; removeAnnotation: (id: string) => void; // Templates templates: Template[]; activeTemplateId: string | null; activeClassId: string | null; activeClass: TemplateClass | null; setTemplates: (templates: Template[]) => void; setActiveTemplateId: (id: string | null) => void; setActiveClassId: (id: string | null) => void; setActiveClass: (templateClass: TemplateClass | null) => void; addTemplate: (template: Template) => void; updateTemplate: (template: Template) => void; removeTemplate: (id: string) => void; // UI isLoading: boolean; error: string | null; setLoading: (loading: boolean) => void; setError: (error: string | null) => void; } export const useStore = create((set) => ({ // Auth isAuthenticated: false, token: null, login: (token: string) => { localStorage.setItem('token', token); set({ isAuthenticated: true, token }); }, logout: () => { localStorage.removeItem('token'); set({ isAuthenticated: false, token: null, currentProject: null, projects: [], templates: [], frames: [], annotations: [], masks: [], maskHistory: [], maskFuture: [], activeTemplateId: null, activeClassId: null, activeClass: null, }); }, // Projects projects: [], currentProject: null, setProjects: (projects: Project[]) => set({ projects }), setCurrentProject: (currentProject: Project | null) => set({ currentProject }), addProject: (project: Project) => set((state) => ({ projects: [project, ...state.projects] })), updateProject: (project: Project) => set((state) => ({ projects: state.projects.map((p) => (p.id === project.id ? project : p)), })), // Workspace activeModule: 'workspace', activeTool: 'move', aiModel: 'sam2', frames: [], currentFrameIndex: 0, annotations: [], masks: [], maskHistory: [], maskFuture: [], setActiveModule: (activeModule: string) => set({ activeModule }), setActiveTool: (activeTool: string) => set({ activeTool }), setAiModel: (aiModel: AiModelId) => set({ aiModel }), setFrames: (frames: Frame[]) => set({ frames }), setCurrentFrame: (currentFrameIndex: number) => set({ currentFrameIndex }), addAnnotation: (annotation: Annotation) => set((state) => ({ annotations: [...state.annotations, annotation] })), addMask: (mask: Mask) => set((state) => ({ masks: [...state.masks, mask], maskHistory: [...state.maskHistory, state.masks], maskFuture: [], })), updateMask: (id: string, updates: Partial) => set((state) => ({ masks: state.masks.map((mask) => (mask.id === id ? { ...mask, ...updates } : mask)), maskHistory: [...state.maskHistory, state.masks], maskFuture: [], })), setMasks: (masks: Mask[]) => set((state) => { const isInitialHydration = state.masks.length === 0 && state.maskHistory.length === 0 && state.maskFuture.length === 0; return { masks, maskHistory: isInitialHydration ? [] : [...state.maskHistory, state.masks], maskFuture: [], }; }), clearMasks: () => set((state) => ({ masks: [], maskHistory: [...state.maskHistory, state.masks], maskFuture: [], })), undoMasks: () => set((state) => { if (state.maskHistory.length === 0) return state; const previous = state.maskHistory[state.maskHistory.length - 1]; return { masks: previous, maskHistory: state.maskHistory.slice(0, -1), maskFuture: [state.masks, ...state.maskFuture], }; }), redoMasks: () => set((state) => { if (state.maskFuture.length === 0) return state; const [next, ...rest] = state.maskFuture; return { masks: next, maskHistory: [...state.maskHistory, state.masks], maskFuture: rest, }; }), removeAnnotation: (id: string) => set((state) => ({ annotations: state.annotations.filter((a) => a.id !== id), })), // Templates templates: [], activeTemplateId: null, activeClassId: null, activeClass: null, setTemplates: (templates: Template[]) => set({ templates }), setActiveTemplateId: (activeTemplateId: string | null) => set({ activeTemplateId }), setActiveClassId: (activeClassId: string | null) => set({ activeClassId }), setActiveClass: (activeClass: TemplateClass | null) => set({ activeClass, activeClassId: activeClass?.id || null, }), addTemplate: (template: Template) => set((state) => ({ templates: [...state.templates, template] })), updateTemplate: (template: Template) => set((state) => ({ templates: state.templates.map((t) => (t.id === template.id ? template : t)), })), removeTemplate: (id: string) => set((state) => ({ templates: state.templates.filter((t) => t.id !== id), })), // UI isLoading: false, error: null, setLoading: (isLoading: boolean) => set({ isLoading }), setError: (error: string | null) => set({ error }), }));