diff --git a/api/DTO/movie_start_dto.ts b/api/DTO/movie_start_dto.ts index 0180ed8..72e621a 100644 --- a/api/DTO/movie_start_dto.ts +++ b/api/DTO/movie_start_dto.ts @@ -334,3 +334,43 @@ export interface TextToImageResponse { /** 是否成功 */ successful: boolean; } + +/** + * 创建电影V4接口请求数据 + */ +export interface CreateMovieProjectV4Request { + /** 剧本内容 */ + script: string; + /** 模式 */ + mode: "auto" | "manual"; + /** 分辨率 */ + resolution: "720p" | "1080p" | "4k"; + /** 语言 */ + language: string; + /** 画面比例(横/竖屏) */ + aspect_ratio: AspectRatioValue; + /** 是否是图生 */ + is_image_to_video: boolean; + /** pcode编码 */ + pcode: string; + /** 角色简介数组 */ + character_briefs?: { + character_name:string; + character_description:string; + image_url:string; + }[]; + /** 场景简介数组 */ + scene_briefs?: { + scene_name:string; + scene_description:string; + image_url:string; + scene_type:string; + }[]; + /** 道具简介数组 */ + prop_briefs?: { + prop_name:string; + prop_description:string; + image_url:string; + prop_type:string; + }[]; +} \ No newline at end of file diff --git a/api/create_movie.ts b/api/create_movie.ts index 89db035..3801262 100644 --- a/api/create_movie.ts +++ b/api/create_movie.ts @@ -1,6 +1,7 @@ import { CreateMovieProjectV2Request, CreateMovieProjectV3Request, + CreateMovieProjectV4Request } from "./DTO/movie_start_dto"; import { post } from "./request"; import { getClientUserData } from './common'; @@ -40,4 +41,18 @@ export const createMovieProjectV3 = async ( post("/movie/create_movie_project_v3", params); const user_data = getClientUserData(); return withQueuePolling(apiCall, { ...(request as any), user_data } as any); +}; + +/** + * 创建电影项目V4 + * @param request - 创建项目请求参数 + * @returns Promise + */ +export const createMovieProjectV4 = async ( + request: CreateMovieProjectV4Request +): Promise => { + const apiCall = (params: CreateMovieProjectV4Request) => + post("/movie/create_movie_project_v4", params); + const user_data = getClientUserData(); + return withQueuePolling(apiCall, { ...(request as any), user_data } as any); }; \ No newline at end of file diff --git a/app/service/Interaction/MovieProjectService.ts b/app/service/Interaction/MovieProjectService.ts index 428bd44..6c7f1d7 100644 --- a/app/service/Interaction/MovieProjectService.ts +++ b/app/service/Interaction/MovieProjectService.ts @@ -1,5 +1,4 @@ - -import { createMovieProject, createMovieProjectV2, createMovieProjectV3 } from "@/api/create_movie"; +import { createMovieProject, createMovieProjectV2, createMovieProjectV3, createMovieProjectV4 } from "@/api/create_movie"; import { QueueResponse, withQueuePolling, QueueResponseData } from "@/api/movie_queue"; /** @@ -11,7 +10,9 @@ export enum MovieProjectMode { /** 照片生成模式 */ IMAGE = "image", /** 模板生成模式 */ - TEMPLATE = "template" + TEMPLATE = "template", + /** V4增强模式(支持角色、场景、道具等) */ + V4 = "v4" } /** 创建项目响应数据 */ @@ -43,6 +44,9 @@ export class MovieProjectService { case MovieProjectMode.TEMPLATE: apiCall = createMovieProjectV3 as (p: T) => Promise; break; + case MovieProjectMode.V4: + apiCall = createMovieProjectV4 as (p: T) => Promise; + break; default: throw new Error(`不支持的创建模式: ${mode}`); } diff --git a/components/pages/create-video/CreateInput/VideoCreationForm.tsx b/components/pages/create-video/CreateInput/VideoCreationForm.tsx index 42c52df..9310014 100644 --- a/components/pages/create-video/CreateInput/VideoCreationForm.tsx +++ b/components/pages/create-video/CreateInput/VideoCreationForm.tsx @@ -18,6 +18,9 @@ import { AddItemModal } from './AddItemModal'; import { defaultConfig } from './config-options'; import type { ConfigOptions } from './config-options'; import { useDeviceType } from '@/hooks/useDeviceType'; +import { MovieProjectService, MovieProjectMode } from '@/app/service/Interaction/MovieProjectService'; +import type { CreateMovieProjectV4Request } from '@/api/DTO/movie_start_dto'; +import { getCurrentUser } from '@/lib/auth'; export default function VideoCreationForm() { const [photos, setPhotos] = useState([]); @@ -28,6 +31,7 @@ export default function VideoCreationForm() { const [currentItemType, setCurrentItemType] = useState(null); const [editingIndex, setEditingIndex] = useState(null); const [replacingIndex, setReplacingIndex] = useState(null); + const [isCreating, setIsCreating] = useState(false); const { isMobile, isDesktop } = useDeviceType(); @@ -74,13 +78,6 @@ export default function VideoCreationForm() { setConfigOptions(prev => ({ ...prev, [key]: value })); }; - /** Handle opening add item modal */ - const handleOpenAddItemModal = (type: PhotoType) => { - setCurrentItemType(type); - setEditingIndex(null); - setAddItemModalVisible(true); - }; - /** Handle editing a photo */ const handleEditPhoto = (index: number) => { const photo = photos[index]; @@ -165,13 +162,89 @@ export default function VideoCreationForm() { }; /** Handle video creation */ - const handleCreate = () => { - console.log({ - text: inputText, - photos, - config: configOptions, - }); - // TODO: Implement video creation logic + const handleCreate = async () => { + if (isCreating) return; + + if (!inputText.trim()) { + window.msg?.warning('Please enter your story description'); + return; + } + + try { + setIsCreating(true); + + const user = getCurrentUser(); + if (!user?.id) { + window.msg?.error('Please login first'); + return; + } + + /** Separate photos by type */ + const characterPhotos = photos.filter(p => p.type === 'character'); + const scenePhotos = photos.filter(p => p.type === 'scene'); + const propPhotos = photos.filter(p => p.type === 'prop'); + + /** Build request parameters */ + const requestParams: CreateMovieProjectV4Request = { + script: inputText, + mode: configOptions.expansion_mode ? 'auto' : 'manual', + resolution: '720p', + language: configOptions.language, + aspect_ratio: configOptions.aspect_ratio, + is_image_to_video: photos.length > 0, + pcode: configOptions.pcode, + }; + + /** Add character briefs if exists */ + if (characterPhotos.length > 0) { + requestParams.character_briefs = characterPhotos.map(photo => ({ + character_name: photo.name || 'Character', + character_description: photo.description || '', + image_url: photo.url, + })); + } + + /** Add scene briefs if exists */ + if (scenePhotos.length > 0) { + requestParams.scene_briefs = scenePhotos.map(photo => ({ + scene_name: photo.name || 'Scene', + scene_description: photo.description || '', + image_url: photo.url, + scene_type: 'custom', + })); + } + + /** Add prop briefs if exists */ + if (propPhotos.length > 0) { + requestParams.prop_briefs = propPhotos.map(photo => ({ + prop_name: photo.name || 'Prop', + prop_description: photo.description || '', + image_url: photo.url, + prop_type: 'custom', + })); + } + + console.log('Creating video with params:', requestParams); + + /** Call MovieProjectService V4 API */ + const result = await MovieProjectService.createProject( + MovieProjectMode.V4, + requestParams + ); + + console.log('Video creation successful, project_id:', result.project_id); + window.msg?.success(`Video created successfully! Project ID: ${result.project_id}`); + + /** TODO: Navigate to project detail page or next step */ + // window.location.href = `/movies/${result.project_id}`; + } catch (error) { + console.error('Failed to create video:', error); + if (error instanceof Error && error.message !== '操作已取消') { + window.msg?.error(error.message || 'Failed to create video'); + } + } finally { + setIsCreating(false); + } }; return ( @@ -298,10 +371,15 @@ export default function VideoCreationForm() { {/* Right Side - Create Button */}