统一创建入口:V4接口

This commit is contained in:
北枳 2025-10-20 20:20:59 +08:00
parent bb56a0bdaf
commit 680ddd7cc6
4 changed files with 156 additions and 19 deletions

View File

@ -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;
}[];
}

View File

@ -1,6 +1,7 @@
import {
CreateMovieProjectV2Request,
CreateMovieProjectV3Request,
CreateMovieProjectV4Request
} from "./DTO/movie_start_dto";
import { post } from "./request";
import { getClientUserData } from './common';
@ -41,3 +42,17 @@ export const createMovieProjectV3 = async (
const user_data = getClientUserData();
return withQueuePolling(apiCall, { ...(request as any), user_data } as any);
};
/**
* V4
* @param request -
* @returns Promise<QueueResponse>
*/
export const createMovieProjectV4 = async (
request: CreateMovieProjectV4Request
): Promise<QueueResponse> => {
const apiCall = (params: CreateMovieProjectV4Request) =>
post<QueueResponse>("/movie/create_movie_project_v4", params);
const user_data = getClientUserData();
return withQueuePolling(apiCall, { ...(request as any), user_data } as any);
};

View File

@ -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<QueueResponse>;
break;
case MovieProjectMode.V4:
apiCall = createMovieProjectV4 as (p: T) => Promise<QueueResponse>;
break;
default:
throw new Error(`不支持的创建模式: ${mode}`);
}

View File

@ -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<PhotoItem[]>([]);
@ -28,6 +31,7 @@ export default function VideoCreationForm() {
const [currentItemType, setCurrentItemType] = useState<PhotoType | null>(null);
const [editingIndex, setEditingIndex] = useState<number | null>(null);
const [replacingIndex, setReplacingIndex] = useState<number | null>(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<CreateMovieProjectV4Request>(
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 */}
<button
data-alt="create-button"
className="w-8 h-8 rounded-full bg-black hover:bg-gray-900 border border-white/20 hover:border-cyan-400/60 transition-all duration-200 flex items-center justify-center text-white shadow-lg hover:shadow-cyan-400/20"
className="w-8 h-8 rounded-full bg-black hover:bg-gray-900 border border-white/20 hover:border-cyan-400/60 transition-all duration-200 flex items-center justify-center text-white shadow-lg hover:shadow-cyan-400/20 disabled:opacity-50 disabled:cursor-not-allowed"
onClick={handleCreate}
disabled={isCreating}
>
<ArrowRightOutlined className="text-base font-bold" />
{isCreating ? (
<span className="animate-spin inline-block w-4 h-4 border-2 border-white border-t-transparent rounded-full" />
) : (
<ArrowRightOutlined className="text-base font-bold" />
)}
</button>
</div>
</div>