import http from "@/api/http"; export interface CreativeStyle { id: string; name: string; image_url?: string; reference_image_asset_id?: string; reference_image_url?: string; } export interface VideoReferenceCapability { supported: boolean; max_count: number; min_total_duration_ms?: number; max_total_duration_ms?: number; min_short_side_pixels?: number; max_short_side_pixels?: number; } export interface VideoModelCapabilities { reference_images: VideoReferenceCapability; reference_audios: VideoReferenceCapability; reference_videos: VideoReferenceCapability; } export interface CreativeModel { id: string; name: string; model_type: "text" | "image" | "video"; channel_name: string; multimodal?: boolean; text_billing_mode?: "per_request" | "per_token"; capabilities?: VideoModelCapabilities; } export interface CreativePrice { model_id: string; price_key: string; unit: string; price: number; } export interface CreativeOptions { styles: CreativeStyle[]; models: CreativeModel[]; prices?: CreativePrice[]; } export interface PromptPreset { id: string; name: string; type: string; content: string; scope?: "system" | "user"; editable?: boolean; } export interface ModelConfig { purpose: string; model_id: string; model_name: string; model_type: "text" | "image" | "video"; channel_name: string; settings?: { aspect_ratio?: string; resolution?: string; prompt?: string }; capabilities?: VideoModelCapabilities; } export interface ProjectSummary { id: string; name: string; project_type: string; style_name: string; era_type: string; custom_era?: string; aspect_ratio: string; localization: string; short_drama_type?: string; play_count?: string; audience_profile?: string; producer?: string; cast_members: ProjectCastMember[]; cover_url?: string; cover_mime_type?: string; cover_video_url?: string; episode_count: number; processing_count: number; updated_at: string; } export interface ProjectCastMember { name: string; follower_count?: string; } /** 剧集摘要,包含项目列表和工作台所需的处理状态与产出标记。 */ export interface Episode { id: string; episode_no: number; name: string; audio_source: "video_audio" | "subtitle_file"; source_language?: string; status: string; analysis_message?: string; source_video_asset_id?: string; source_video_url?: string; source_video_original_name?: string; subtitle_asset_id?: string; subtitle_original_name?: string; cover_url?: string; cover_video_url?: string; storyboard_count: number; completed_storyboard_count: number; has_redraw_script?: boolean; has_text_source?: boolean; updated_at?: string; } export interface ProjectDetail extends ProjectSummary { style_id: string; style_image_url?: string; model_configs: ModelConfig[]; episodes: Episode[]; } export interface ProjectAsset { id: string; project_id: string; source_episode_id?: string; asset_type: "character" | "scene" | "prop" | "custom"; name: string; description?: string; image_prompt?: string; appearances?: unknown[]; image_asset_id?: string; audio_asset_id?: string; audio_duration_ms?: number; image_url?: string; audio_url?: string; video_url?: string; user_edited: boolean; } export interface Storyboard { id: string; episode_id: string; sequence_no: number; start_ms: number; end_ms: number; duration_seconds: number; title?: string; thumbnail_asset_id?: string; thumbnail_url?: string; script_content?: string; source_excerpt?: string; prompt_content?: string; image_prompt?: string; dialogue?: unknown[]; asset_refs?: Array<{ id: string; name?: string }>; locked: boolean; active_output_id?: string; status: string; output_id?: string; result_media_id?: string; result_url?: string; result_mime?: string; history_count: number; task_status?: string; } export interface WorkbenchData { project: ProjectDetail; episode: Episode; assets: ProjectAsset[]; storyboards: Storyboard[]; } export interface RedrawSource { source_video_asset_id?: string; source_video_url?: string; source_video_original_name?: string; subtitle_asset_id?: string; subtitle_url?: string; subtitle_original_name?: string; audio_source: "video_audio" | "subtitle_file"; source_language?: string; status: string; analysis_message?: string; } export interface RedrawWorkbenchData { project: ProjectDetail; episode: Episode; source: RedrawSource; assets: ProjectAsset[]; storyboards: Storyboard[]; script_content: string; } export interface DramaChapterPreview { title: string; char_count: number; } export interface DramaImportPreview { import_token: string; has_chapters: boolean; total_characters: number; chapters: DramaChapterPreview[]; single_episode_confirmation_required: boolean; } export interface DramaEpisodeSource { id: string; episode_id: string; title?: string; raw_content: string; char_count: number; source_type: string; source_filename?: string; content_sha256: string; } export interface DramaParseTask { id: string; request_id: string; batch_id?: string; episode_id: string; status: string; input_tokens?: number; output_tokens?: number; total_tokens?: number; estimated_points?: string; prepaid_points?: string; actual_points?: string; cost_refunded?: boolean; token_count_source?: string; error_code?: string; error_message?: string; created_at: string; finished_at?: string; } export interface GenerationTask { id: string; request_id: string; task_type: string; status: string; episode_id?: string; storyboard_id?: string; mode?: string; phase_started_at?: string; sequence_no?: number; asset_id?: string; estimated_points?: string; actual_points?: string; error_code?: string; error_message?: string; result_url?: string; created_at: string; submitted_at?: string; finished_at?: string; last_result_at?: string; } export interface GenerationOutput { id: string; task_id: string; media_asset_id: string; output_type: string; public_url: string; mime_type: string; size_bytes: number; estimated_points?: string; actual_points?: string; active: boolean; candidate?: boolean; created_at: string; } export interface ProjectPayload { project_type?: "video_redraw" | "premium_drama"; name: string; style_id: string; era_type: string; custom_era?: string | null; aspect_ratio: string; localization?: string | null; short_drama_type?: string; play_count?: string; audience_profile?: string; producer?: string; cast_members?: ProjectCastMember[]; } export interface ScriptAnalysis { id: string; user_id: string; name: string; source_content: string; result_content: string; analysis_result: ScriptAnalysisResult; analysis_status: "idle" | "queued" | "running" | "succeeded" | "failed"; analysis_message?: string; characters?: ScriptAnalysisCharacter[]; created_at: string; updated_at: string; } export interface ScriptAnalysisImportProject { id: string; name: string; episode_count: number; char_count: number; updated_at: string; } export interface ScriptAnalysisCharacter { id: string; script_analysis_id: string; name: string; faction: "正派" | "反派" | "中立派"; biography: string; motivation: string; relationship: string; sort_order: number; } export interface ScriptAnalysisResult { tags?: { drama_tags?: string[]; applicable_scene_tags?: string[] }; selling_point?: { summary?: string; highlights?: Array<{ type: string; content: string }> }; story_structure?: { synopsis?: string; story_outline?: string[]; emotion_outline?: Array<{ stage: string; emotion: string; description: string }>; episode_outlines?: Array<{ episode_no: number; title: string; outline: string; emotion: string }>; characters?: Array<{ name: string; faction: "正派" | "反派" | "中立派"; biography: string; motivation: string; relationship: string; }>; }; opening_hook?: { first_scene?: string; first_line?: string; attention_methods?: string[]; analysis?: string; optimization?: string; }; episode_hooks?: Array<{ episode_no: number; hook_type: string; hook: string; basis: string }>; episode_10_paywall_hooks?: Array<{ hook_type: string; content: string; appeal: string }>; custom_analysis?: Array<{ objective: string; conclusion: string; evidence: string[] }>; unmatched_objectives?: Array<{ objective: string; reason: string }>; } const data = (response: { data: { data: T } }) => response.data.data; export const creativeApi = { options: () => http.get("/creative/options").then(data), allProjects: () => http.get("/projects").then(data), projects: (keyword = "") => http.get("/projects", { params: { project_type: "video_redraw", keyword } }).then(data), dramaProjects: (keyword = "") => http.get("/projects", { params: { project_type: "premium_drama", keyword } }).then(data), createProject: (payload: ProjectPayload) => http.post("/projects", payload).then(data<{ id: string }>), project: (projectId: string) => http.get(`/projects/${projectId}`).then(data), updateProject: (projectId: string, payload: ProjectPayload) => http.put(`/projects/${projectId}`, payload), deleteProject: (projectId: string) => http.delete(`/projects/${projectId}`), saveModels: (projectId: string, payload: Record) => http.put(`/projects/${projectId}/model-configs`, payload), promptPresets: (type: string) => http.get(`/user/prompts/${encodeURIComponent(type)}`).then(data<{ prompts: PromptPreset[]; selected_id?: string }>), selectPrompt: (type: string, promptId?: string) => http.put(`/user/prompts/${encodeURIComponent(type)}/select`, { prompt_id: promptId || null }), createCustomPrompt: (payload: { name: string; type: string; content: string }) => http.post("/user/custom-prompts", payload).then(data), updateCustomPrompt: (id: string, payload: { name: string; type?: string; content: string }) => http.put(`/user/custom-prompts/${id}`, payload).then(data), deleteCustomPrompt: (id: string) => http.delete(`/user/custom-prompts/${id}`), createEpisode: (projectId: string, payload: { episode_no?: number; name?: string } = {}) => http.post(`/projects/${projectId}/episodes`, payload).then(data), updateEpisodeName: (projectId: string, episodeId: string, name: string) => http.put(`/projects/${projectId}/episodes/${episodeId}`, { name }).then(data), previewDramaImport: (projectId: string, payload: { file?: File; rawText?: string }) => { const form = new FormData(); if (payload.file) form.append("file", payload.file); if (payload.rawText) form.append("raw_text", payload.rawText); return http .post(`/projects/${projectId}/episodes/import/preview`, form, { timeout: 0, headers: { "Content-Type": "multipart/form-data" }, }) .then(data); }, confirmDramaImport: ( projectId: string, payload: { import_token: string; chapters_per_episode: number; start_episode_no: number; treat_as_single_episode: boolean; }, ) => http.post(`/projects/${projectId}/episodes/import/confirm`, payload).then(data), dramaEpisodeSource: (projectId: string, episodeId: string) => http.get(`/projects/${projectId}/episodes/${episodeId}/text-source`).then(data), saveDramaEpisodeSource: (projectId: string, episodeId: string, payload: { title?: string; raw_content: string }) => http.put(`/projects/${projectId}/episodes/${episodeId}/text-source`, payload).then(data), parseDramaEpisode: (projectId: string, episodeId: string) => http.post(`/projects/${projectId}/episodes/${episodeId}/parse`).then(data), reparseDramaEpisode: (projectId: string, episodeId: string) => http.post(`/projects/${projectId}/episodes/${episodeId}/reparse`).then(data), createDramaStoryboard: (projectId: string, episodeId: string) => http.post(`/projects/${projectId}/episodes/${episodeId}/storyboards`).then(data), dramaParseTasks: (projectId: string, episodeId?: string) => http.get(`/projects/${projectId}/parse-tasks`, { params: { episode_id: episodeId } }).then(data), cancelDramaParse: (projectId: string, taskId: string) => http.post(`/projects/${projectId}/parse-tasks/${taskId}/cancel`), deleteEpisode: (projectId: string, episodeId: string) => http.delete(`/projects/${projectId}/episodes/${episodeId}`), workbench: (projectId: string, episodeId: string) => http.get(`/projects/${projectId}/episodes/${episodeId}/workbench`).then(data), // episodeVideosArchive 下载当前剧集按项目类型筛选后的全部视频 ZIP。 episodeVideosArchive: (projectId: string, episodeId: string) => http .get(`/projects/${projectId}/episodes/${episodeId}/videos.zip`, { responseType: "blob", timeout: 0 }) .then((response) => response.data as Blob), redrawWorkbench: (projectId: string, episodeId: string) => http.get(`/projects/${projectId}/episodes/${episodeId}/redraw-workbench`).then(data), uploadSource: (projectId: string, episodeId: string, file: File) => upload(`/projects/${projectId}/episodes/${episodeId}/source`, file), uploadSubtitle: (projectId: string, episodeId: string, file: File) => upload(`/projects/${projectId}/episodes/${episodeId}/subtitle`, file), uploadRedrawSource: (projectId: string, file: File) => upload(`/projects/${projectId}/redraw-source`, file), uploadRedrawSubtitle: (projectId: string, file: File) => upload(`/projects/${projectId}/redraw-subtitle`, file), saveAnalysisSettings: ( projectId: string, episodeId: string, payload: { audio_source: string; source_language: string }, ) => http.put(`/projects/${projectId}/episodes/${episodeId}/analysis-settings`, payload), saveRedrawAnalysisSettings: (projectId: string, payload: { audio_source: string; source_language: string }) => http.put(`/projects/${projectId}/redraw-analysis-settings`, payload), analyze: (projectId: string, episodeId: string) => http.post(`/projects/${projectId}/episodes/${episodeId}/analyze`).then(data), reanalyze: (projectId: string, episodeId: string) => http.post(`/projects/${projectId}/episodes/${episodeId}/reanalyze`).then(data), analyzeRedraw: (projectId: string) => http.post(`/projects/${projectId}/redraw-analyze`).then(data), reanalyzeRedraw: (projectId: string) => http.post(`/projects/${projectId}/redraw-reanalyze`).then(data), reverseRedrawScript: (projectId: string, episodeId: string) => http.post(`/projects/${projectId}/episodes/${episodeId}/redraw-script`).then(data), saveRedrawScript: (projectId: string, episodeId: string, content: string) => http.put(`/projects/${projectId}/episodes/${episodeId}/redraw-script`, { content }), createAsset: (projectId: string, payload: { asset_type: string; name: string }) => http.post(`/projects/${projectId}/assets`, payload).then(data), updateAsset: (projectId: string, assetId: string, payload: Partial) => http.put(`/projects/${projectId}/assets/${assetId}`, payload), deleteAsset: (projectId: string, assetId: string) => http.delete(`/projects/${projectId}/assets/${assetId}`), uploadAssetImage: (projectId: string, assetId: string, file: File) => upload(`/projects/${projectId}/assets/${assetId}/image`, file), removeAssetImage: (projectId: string, assetId: string) => http.delete(`/projects/${projectId}/assets/${assetId}/image`), uploadAssetAudio: (projectId: string, assetId: string, file: File, durationMs?: number) => upload( `/projects/${projectId}/assets/${assetId}/audio`, file, durationMs === undefined ? undefined : { duration_ms: String(durationMs) }, ), updateStoryboard: (projectId: string, storyboardId: string, payload: Partial) => http.put(`/projects/${projectId}/storyboards/${storyboardId}`, payload), insertStoryboard: (projectId: string, storyboardId: string, position: "before" | "after") => http.post(`/projects/${projectId}/storyboards/${storyboardId}/insert`, { position }).then(data), deleteStoryboard: (projectId: string, storyboardId: string) => http.delete(`/projects/${projectId}/storyboards/${storyboardId}`), reinferStoryboard: (projectId: string, episodeId: string, storyboardId: string) => http .post(`/projects/${projectId}/episodes/${episodeId}/storyboards/${storyboardId}/reinfer`) .then(data), reinferRedrawStoryboard: (projectId: string, storyboardId: string) => http.post(`/projects/${projectId}/storyboards/${storyboardId}/redraw-reinfer`).then(data), generate: ( projectId: string, episodeId: string, storyboardId: string, taskType: "image_generation" | "video_generation", input: Record, ) => http .post(`/projects/${projectId}/episodes/${episodeId}/storyboards/${storyboardId}/generate`, { task_type: taskType, input, }) .then(data), tasks: (projectId: string, episodeId?: string) => http.get(`/projects/${projectId}/tasks`, { params: { episode_id: episodeId } }).then(data), cancelTask: (projectId: string, taskId: string) => http.post(`/projects/${projectId}/tasks/${taskId}/cancel`), outputs: (projectId: string, storyboardId: string) => http.get(`/projects/${projectId}/storyboards/${storyboardId}/outputs`).then(data), removeStoryboardImage: (projectId: string, storyboardId: string) => http.delete(`/projects/${projectId}/storyboards/${storyboardId}/image`), activateOutput: (projectId: string, storyboardId: string, outputId: string) => http.put(`/projects/${projectId}/storyboards/${storyboardId}/outputs/${outputId}/activate`), addStoryboardCandidate: (projectId: string, storyboardId: string, outputId: string) => http.put(`/projects/${projectId}/storyboards/${storyboardId}/outputs/${outputId}/candidate`), removeStoryboardCandidate: (projectId: string, storyboardId: string, outputId: string) => http.delete(`/projects/${projectId}/storyboards/${storyboardId}/outputs/${outputId}/candidate`), deleteOutput: (projectId: string, storyboardId: string, outputId: string) => http.delete(`/projects/${projectId}/storyboards/${storyboardId}/outputs/${outputId}`), assetOutputs: (projectId: string, assetId: string) => http.get(`/projects/${projectId}/assets/${assetId}/outputs`).then(data), activateAssetOutput: (projectId: string, assetId: string, outputId: string) => http.put(`/projects/${projectId}/assets/${assetId}/outputs/${outputId}/activate`), deleteAssetOutput: (projectId: string, assetId: string, outputId: string) => http.delete(`/projects/${projectId}/assets/${assetId}/outputs/${outputId}`), media: (mediaId: string) => http.get(`/media/${mediaId}`, { responseType: "blob", timeout: 0 }).then((response) => response.data as Blob), scriptAnalyses: () => http.get("/script-analyses").then(data), createScriptAnalysis: () => http.post("/script-analyses").then(data), scriptAnalysisImportProjects: () => http.get("/script-analyses/import-projects").then(data), scriptAnalysis: (id: string) => http.get(`/script-analyses/${id}`).then(data), updateScriptAnalysis: (id: string, payload: { name: string; source_content: string; result_content: string }) => http.put(`/script-analyses/${id}`, payload).then(data), deleteScriptAnalysis: (id: string) => http.delete(`/script-analyses/${id}`), importScriptAnalysisProject: (id: string, projectId: string) => http.post(`/script-analyses/${id}/import-project`, { project_id: projectId }).then(data), importScriptAnalysisFile: (id: string, file: File) => upload(`/script-analyses/${id}/import-file`, file), analyzeScriptAnalysis: (id: string) => http.post(`/script-analyses/${id}/analyze`).then(data), cancelScriptAnalysis: (id: string) => http.post(`/script-analyses/${id}/cancel`), userModelConfigs: (scope: "script_analysis") => http.get(`/user/model-configs/${scope}`).then(data), saveUserModelConfig: (scope: "script_analysis", modelType: "text", modelId: string) => http.put(`/user/model-configs/${scope}/${modelType}`, { model_id: modelId }), }; function upload>(path: string, file: File, fields?: Record) { const form = new FormData(); form.append("file", file); Object.entries(fields || {}).forEach(([key, value]) => form.append(key, value)); return http.post(path, form, { timeout: 0, headers: { "Content-Type": "multipart/form-data" } }).then(data); }