初始化

This commit is contained in:
Ran
2026-08-25 17:59:42 +08:00
commit 4b7380dd9b
408 changed files with 327400 additions and 0 deletions
+519
View File
@@ -0,0 +1,519 @@
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 = <T>(response: { data: { data: T } }) => response.data.data;
export const creativeApi = {
options: () => http.get("/creative/options").then(data<CreativeOptions>),
allProjects: () => http.get("/projects").then(data<ProjectSummary[]>),
projects: (keyword = "") =>
http.get("/projects", { params: { project_type: "video_redraw", keyword } }).then(data<ProjectSummary[]>),
dramaProjects: (keyword = "") =>
http.get("/projects", { params: { project_type: "premium_drama", keyword } }).then(data<ProjectSummary[]>),
createProject: (payload: ProjectPayload) => http.post("/projects", payload).then(data<{ id: string }>),
project: (projectId: string) => http.get(`/projects/${projectId}`).then(data<ProjectDetail>),
updateProject: (projectId: string, payload: ProjectPayload) => http.put(`/projects/${projectId}`, payload),
deleteProject: (projectId: string) => http.delete(`/projects/${projectId}`),
saveModels: (projectId: string, payload: Record<string, unknown>) =>
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<PromptPreset>),
updateCustomPrompt: (id: string, payload: { name: string; type?: string; content: string }) =>
http.put(`/user/custom-prompts/${id}`, payload).then(data<PromptPreset>),
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<Episode>),
updateEpisodeName: (projectId: string, episodeId: string, name: string) =>
http.put(`/projects/${projectId}/episodes/${episodeId}`, { name }).then(data<Episode>),
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<DramaImportPreview>);
},
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<Episode[]>),
dramaEpisodeSource: (projectId: string, episodeId: string) =>
http.get(`/projects/${projectId}/episodes/${episodeId}/text-source`).then(data<DramaEpisodeSource>),
saveDramaEpisodeSource: (projectId: string, episodeId: string, payload: { title?: string; raw_content: string }) =>
http.put(`/projects/${projectId}/episodes/${episodeId}/text-source`, payload).then(data<DramaEpisodeSource>),
parseDramaEpisode: (projectId: string, episodeId: string) =>
http.post(`/projects/${projectId}/episodes/${episodeId}/parse`).then(data<DramaParseTask>),
reparseDramaEpisode: (projectId: string, episodeId: string) =>
http.post(`/projects/${projectId}/episodes/${episodeId}/reparse`).then(data<DramaParseTask>),
createDramaStoryboard: (projectId: string, episodeId: string) =>
http.post(`/projects/${projectId}/episodes/${episodeId}/storyboards`).then(data<Storyboard>),
dramaParseTasks: (projectId: string, episodeId?: string) =>
http.get(`/projects/${projectId}/parse-tasks`, { params: { episode_id: episodeId } }).then(data<DramaParseTask[]>),
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<WorkbenchData>),
// 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<RedrawWorkbenchData>),
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<GenerationTask>),
reanalyze: (projectId: string, episodeId: string) =>
http.post(`/projects/${projectId}/episodes/${episodeId}/reanalyze`).then(data<GenerationTask>),
analyzeRedraw: (projectId: string) => http.post(`/projects/${projectId}/redraw-analyze`).then(data<GenerationTask>),
reanalyzeRedraw: (projectId: string) =>
http.post(`/projects/${projectId}/redraw-reanalyze`).then(data<GenerationTask>),
reverseRedrawScript: (projectId: string, episodeId: string) =>
http.post(`/projects/${projectId}/episodes/${episodeId}/redraw-script`).then(data<GenerationTask>),
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<ProjectAsset>),
updateAsset: (projectId: string, assetId: string, payload: Partial<ProjectAsset>) =>
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<Storyboard>) =>
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<Storyboard>),
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<GenerationTask>),
reinferRedrawStoryboard: (projectId: string, storyboardId: string) =>
http.post(`/projects/${projectId}/storyboards/${storyboardId}/redraw-reinfer`).then(data<GenerationTask>),
generate: (
projectId: string,
episodeId: string,
storyboardId: string,
taskType: "image_generation" | "video_generation",
input: Record<string, unknown>,
) =>
http
.post(`/projects/${projectId}/episodes/${episodeId}/storyboards/${storyboardId}/generate`, {
task_type: taskType,
input,
})
.then(data<GenerationTask>),
tasks: (projectId: string, episodeId?: string) =>
http.get(`/projects/${projectId}/tasks`, { params: { episode_id: episodeId } }).then(data<GenerationTask[]>),
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<GenerationOutput[]>),
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<GenerationOutput[]>),
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<ScriptAnalysis[]>),
createScriptAnalysis: () => http.post("/script-analyses").then(data<ScriptAnalysis>),
scriptAnalysisImportProjects: () =>
http.get("/script-analyses/import-projects").then(data<ScriptAnalysisImportProject[]>),
scriptAnalysis: (id: string) => http.get(`/script-analyses/${id}`).then(data<ScriptAnalysis>),
updateScriptAnalysis: (id: string, payload: { name: string; source_content: string; result_content: string }) =>
http.put(`/script-analyses/${id}`, payload).then(data<ScriptAnalysis>),
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<ScriptAnalysis>),
importScriptAnalysisFile: (id: string, file: File) =>
upload<ScriptAnalysis>(`/script-analyses/${id}/import-file`, file),
analyzeScriptAnalysis: (id: string) => http.post(`/script-analyses/${id}/analyze`).then(data<GenerationTask>),
cancelScriptAnalysis: (id: string) => http.post(`/script-analyses/${id}/cancel`),
userModelConfigs: (scope: "script_analysis") => http.get(`/user/model-configs/${scope}`).then(data<ModelConfig[]>),
saveUserModelConfig: (scope: "script_analysis", modelType: "text", modelId: string) =>
http.put(`/user/model-configs/${scope}/${modelType}`, { model_id: modelId }),
};
function upload<T = Record<string, unknown>>(path: string, file: File, fields?: Record<string, string>) {
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<T>);
}