/** * Editor API client for connecting to FastAPI backend */ const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api' export interface StoryboardFrame { id: string index: number order: number narration: string image_prompt?: string image_path?: string audio_path?: string video_segment_path?: string duration: number } export interface Storyboard { id: string title: string frames: StoryboardFrame[] total_duration: number final_video_path?: string created_at?: string } export interface PreviewResponse { preview_path: string duration: number frames_count: number } export interface InpaintResponse { image_path: string success: boolean } class EditorApiClient { private baseUrl: string constructor(baseUrl: string = API_BASE) { this.baseUrl = baseUrl } /** * Fetch storyboard by ID */ async getStoryboard(storyboardId: string): Promise { const response = await fetch(`${this.baseUrl}/editor/storyboard/${storyboardId}`) if (!response.ok) { throw new Error(`Failed to fetch storyboard: ${response.statusText}`) } return response.json() } /** * Reorder frames in storyboard */ async reorderFrames(storyboardId: string, order: string[]): Promise { const response = await fetch(`${this.baseUrl}/editor/storyboard/${storyboardId}/reorder`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ order }), }) if (!response.ok) { throw new Error(`Failed to reorder frames: ${response.statusText}`) } return response.json() } /** * Update frame duration */ async updateFrameDuration( storyboardId: string, frameId: string, duration: number ): Promise { const response = await fetch( `${this.baseUrl}/editor/storyboard/${storyboardId}/frames/${frameId}/duration`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ duration }), } ) if (!response.ok) { throw new Error(`Failed to update duration: ${response.statusText}`) } return response.json() } /** * Generate preview video */ async generatePreview( storyboardId: string, startFrame?: number, endFrame?: number ): Promise { const response = await fetch(`${this.baseUrl}/editor/storyboard/${storyboardId}/preview`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ start_frame: startFrame ?? 0, end_frame: endFrame, }), }) if (!response.ok) { throw new Error(`Failed to generate preview: ${response.statusText}`) } return response.json() } /** * Update frame content (narration and/or image prompt) */ async updateFrame( storyboardId: string, frameId: string, data: { narration?: string; image_prompt?: string } ): Promise<{ id: string; narration: string; image_prompt?: string; updated: boolean }> { const response = await fetch( `${this.baseUrl}/editor/storyboard/${storyboardId}/frames/${frameId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), } ) if (!response.ok) { const error = await response.json().catch(() => ({ detail: response.statusText })) throw new Error(error.detail || `Failed to update frame: ${response.statusText}`) } return response.json() } /** * Regenerate image for a frame */ async regenerateImage( storyboardId: string, frameId: string, imagePrompt?: string ): Promise<{ image_path: string; success: boolean }> { const response = await fetch( `${this.baseUrl}/editor/storyboard/${storyboardId}/frames/${frameId}/regenerate-image`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image_prompt: imagePrompt }), } ) if (!response.ok) { const error = await response.json().catch(() => ({ detail: response.statusText })) throw new Error(error.detail || `Failed to regenerate image: ${response.statusText}`) } return response.json() } /** * Regenerate audio for a frame */ async regenerateAudio( storyboardId: string, frameId: string, narration?: string, voice?: string ): Promise<{ audio_path: string; duration: number; success: boolean }> { const response = await fetch( `${this.baseUrl}/editor/storyboard/${storyboardId}/frames/${frameId}/regenerate-audio`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ narration, voice }), } ) if (!response.ok) { const error = await response.json().catch(() => ({ detail: response.statusText })) throw new Error(error.detail || `Failed to regenerate audio: ${response.statusText}`) } return response.json() } /** * Inpaint (局部重绘) image for a frame */ async inpaintImage( storyboardId: string, frameId: string, mask: string, prompt?: string, denoiseStrength?: number ): Promise { const response = await fetch( `${this.baseUrl}/editor/storyboard/${storyboardId}/frames/${frameId}/inpaint`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mask, prompt, denoise_strength: denoiseStrength ?? 0.8, }), } ) if (!response.ok) { const error = await response.json().catch(() => ({ detail: response.statusText })) throw new Error(error.detail || `Failed to inpaint image: ${response.statusText}`) } return response.json() } } // Export singleton instance export const editorApi = new EditorApiClient()