import { create } from 'zustand'; import type { CreationMode, ModelOption, AspectRatio, Duration, GenerationType, UploadedFile } from '../types'; let fileCounter = 0; interface InputBarState { // Generation type generationType: GenerationType; setGenerationType: (type: GenerationType) => void; // Mode mode: CreationMode; setMode: (mode: CreationMode) => void; // Model model: ModelOption; setModel: (model: ModelOption) => void; // Aspect ratio aspectRatio: AspectRatio; setAspectRatio: (ratio: AspectRatio) => void; prevAspectRatio: AspectRatio; // Duration duration: Duration; setDuration: (duration: Duration) => void; prevDuration: Duration; // Prompt prompt: string; setPrompt: (prompt: string) => void; // Editor HTML (rich content with mention tags) editorHtml: string; setEditorHtml: (html: string) => void; // Universal references references: UploadedFile[]; addReferences: (files: File[]) => void; removeReference: (id: string) => void; clearReferences: () => void; // Keyframe firstFrame: UploadedFile | null; lastFrame: UploadedFile | null; setFirstFrame: (file: File | null) => void; setLastFrame: (file: File | null) => void; // Computed canSubmit: () => boolean; // @ trigger (for toolbar button to insert @ in contentEditable) insertAtTrigger: number; triggerInsertAt: () => void; // Actions switchMode: (mode: CreationMode) => void; submit: () => void; reset: () => void; } export const useInputBarStore = create((set, get) => ({ generationType: 'video', setGenerationType: (generationType) => set({ generationType }), mode: 'universal', setMode: (mode) => set({ mode }), model: 'seedance_2.0', setModel: (model) => set({ model }), aspectRatio: '21:9', setAspectRatio: (aspectRatio) => set({ aspectRatio, prevAspectRatio: aspectRatio }), prevAspectRatio: '21:9', duration: 15, setDuration: (duration) => { const state = get(); if (state.mode === 'universal') { set({ duration, prevDuration: duration }); } else { set({ duration }); } }, prevDuration: 15, prompt: '', setPrompt: (prompt) => set({ prompt }), editorHtml: '', setEditorHtml: (editorHtml) => set({ editorHtml }), references: [], addReferences: (files) => { const state = get(); const remaining = 5 - state.references.length; if (remaining <= 0) return; const toAdd = files.slice(0, remaining); const newRefs: UploadedFile[] = toAdd.map((file) => { fileCounter++; const type = file.type.startsWith('video') ? 'video' as const : 'image' as const; const labelPrefix = type === 'video' ? '视频' : '图片'; return { id: `ref_${fileCounter}`, file, type, previewUrl: URL.createObjectURL(file), label: `${labelPrefix}${fileCounter}`, }; }); set({ references: [...state.references, ...newRefs] }); }, removeReference: (id) => { const state = get(); const ref = state.references.find((r) => r.id === id); if (ref) URL.revokeObjectURL(ref.previewUrl); set({ references: state.references.filter((r) => r.id !== id) }); }, clearReferences: () => { const state = get(); state.references.forEach((r) => URL.revokeObjectURL(r.previewUrl)); set({ references: [] }); }, firstFrame: null, lastFrame: null, setFirstFrame: (file) => { const state = get(); if (state.firstFrame) URL.revokeObjectURL(state.firstFrame.previewUrl); if (file) { fileCounter++; set({ firstFrame: { id: `first_${fileCounter}`, file, type: 'image', previewUrl: URL.createObjectURL(file), label: '首帧', }, }); } else { set({ firstFrame: null }); } }, setLastFrame: (file) => { const state = get(); if (state.lastFrame) URL.revokeObjectURL(state.lastFrame.previewUrl); if (file) { fileCounter++; set({ lastFrame: { id: `last_${fileCounter}`, file, type: 'image', previewUrl: URL.createObjectURL(file), label: '尾帧', }, }); } else { set({ lastFrame: null }); } }, canSubmit: () => { const state = get(); const hasText = state.prompt.trim().length > 0; const hasFiles = state.mode === 'universal' ? state.references.length > 0 : state.firstFrame !== null || state.lastFrame !== null; return hasText || hasFiles; }, insertAtTrigger: 0, triggerInsertAt: () => set((s) => ({ insertAtTrigger: s.insertAtTrigger + 1 })), switchMode: (mode) => { const state = get(); if (state.mode === mode) return; if (mode === 'keyframe') { // Clear universal references state.references.forEach((r) => URL.revokeObjectURL(r.previewUrl)); set({ mode, references: [], duration: 5, }); } else { // Clear keyframe if (state.firstFrame) URL.revokeObjectURL(state.firstFrame.previewUrl); if (state.lastFrame) URL.revokeObjectURL(state.lastFrame.previewUrl); set({ mode, firstFrame: null, lastFrame: null, aspectRatio: state.prevAspectRatio, duration: state.prevDuration, }); } }, submit: () => { // Just show a toast - no real API call }, reset: () => { const state = get(); state.references.forEach((r) => URL.revokeObjectURL(r.previewUrl)); if (state.firstFrame) URL.revokeObjectURL(state.firstFrame.previewUrl); if (state.lastFrame) URL.revokeObjectURL(state.lastFrame.previewUrl); set({ mode: 'universal', model: 'seedance_2.0', aspectRatio: '21:9', prevAspectRatio: '21:9', duration: 15, prevDuration: 15, prompt: '', editorHtml: '', references: [], firstFrame: null, lastFrame: null, generationType: 'video', }); }, }));