forked from 77media/video-flow
模板测试
This commit is contained in:
parent
fc19d6e25b
commit
ef53a577d4
@ -84,6 +84,8 @@ export interface StoryAnalysisTask{
|
||||
* 创建电影项目V2请求参数 照片生成电影
|
||||
*/
|
||||
export interface CreateMovieProjectV2Request {
|
||||
/** 任务ID */
|
||||
project_id:string;
|
||||
/** 剧本内容 */
|
||||
script: string;
|
||||
/** 用户ID */
|
||||
@ -98,7 +100,7 @@ export interface CreateMovieProjectV2Request {
|
||||
character_briefs: {
|
||||
name:string;
|
||||
image_url:string;
|
||||
character_analysis:Record<string,any>;
|
||||
character_analysis:string;
|
||||
}[];
|
||||
/** 语言 */
|
||||
language: string;
|
||||
@ -198,7 +200,7 @@ export interface CreateMovieProjectV3Request {
|
||||
user_id: string;
|
||||
/** 模式:auto | manual */
|
||||
mode: "auto" | "manual";
|
||||
/** 分辨率:720p | 1080p | 4k */
|
||||
/** 分辨率:720p | 1080p | "4k" */
|
||||
resolution: "720p" | "1080p" | "4k";
|
||||
/** 语言 */
|
||||
language: string;
|
||||
@ -219,4 +221,85 @@ export interface CreateMovieProjectV3Request {
|
||||
/**声音URL */
|
||||
voice_url: string;
|
||||
}[];
|
||||
/** 可填写的变量字段 */
|
||||
fillable_content?: {
|
||||
/** 字段名称 */
|
||||
field_name: string;
|
||||
/** 字段类型 */
|
||||
field_type: string;
|
||||
/** 字段值 */
|
||||
field_value?: string;
|
||||
/** 字段描述 */
|
||||
field_description?: string;
|
||||
/** 字段元数据 */
|
||||
field_meta?: Record<string, any>;
|
||||
}[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gemini文本转图像请求参数
|
||||
*/
|
||||
export interface GeminiTextToImageRequest {
|
||||
/** 提示词 */
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gemini文本转图像响应数据
|
||||
*/
|
||||
export interface GeminiTextToImageData {
|
||||
/** 生成的图像URL */
|
||||
image_url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gemini文本转图像响应
|
||||
*/
|
||||
export interface GeminiTextToImageResponse {
|
||||
/** 响应码 */
|
||||
code: number;
|
||||
/** 响应消息 */
|
||||
message: string;
|
||||
/** 响应数据 */
|
||||
data: GeminiTextToImageData;
|
||||
/** 是否成功 */
|
||||
successful: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本转图像请求参数
|
||||
*/
|
||||
export interface TextToImageRequest {
|
||||
/** 图像描述 */
|
||||
description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本转图像响应数据
|
||||
*/
|
||||
export interface TextToImageData {
|
||||
/** 生成的图像URL */
|
||||
image_url: string;
|
||||
/** 本地图像路径 */
|
||||
local_image_path: string;
|
||||
/** 是否成功 */
|
||||
success: boolean;
|
||||
/** 描述 */
|
||||
description: string;
|
||||
/** 宽高比 */
|
||||
aspect_ratio: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本转图像响应
|
||||
*/
|
||||
export interface TextToImageResponse {
|
||||
/** 响应码 */
|
||||
code: number;
|
||||
/** 响应消息 */
|
||||
message: string;
|
||||
/** 响应数据 */
|
||||
data: TextToImageData;
|
||||
/** 是否成功 */
|
||||
successful: boolean;
|
||||
}
|
||||
|
||||
@ -6,6 +6,10 @@ import {
|
||||
StoryAnalysisTask,
|
||||
MovieStoryTaskDetail,
|
||||
CreateMovieProjectV3Request,
|
||||
GeminiTextToImageRequest,
|
||||
GeminiTextToImageResponse,
|
||||
TextToImageRequest,
|
||||
TextToImageResponse,
|
||||
} from "./DTO/movie_start_dto";
|
||||
import { get, post } from "./request";
|
||||
import {
|
||||
@ -78,3 +82,31 @@ export const getMovieStoryTask = async (taskId: string) => {
|
||||
`/movie_story/task/${taskId}`
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gemini文本转图像生成
|
||||
* @param request - 文本转图像请求参数
|
||||
* @returns Promise<GeminiTextToImageResponse>
|
||||
*/
|
||||
export const generateGeminiTextToImage = async (
|
||||
request: GeminiTextToImageRequest
|
||||
): Promise<GeminiTextToImageResponse> => {
|
||||
return await post<GeminiTextToImageResponse>(
|
||||
"/gemini-text-to-image/generate",
|
||||
request
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 文本转图像生成
|
||||
* @param request - 文本转图像请求参数
|
||||
* @returns Promise<TextToImageResponse>
|
||||
*/
|
||||
export const generateTextToImage = async (
|
||||
request: TextToImageRequest
|
||||
): Promise<TextToImageResponse> => {
|
||||
return await post<TextToImageResponse>(
|
||||
"/text-to-image/draw",
|
||||
request
|
||||
);
|
||||
};
|
||||
|
||||
@ -103,6 +103,7 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
const [taskProgress, setTaskProgress] = useState(0);
|
||||
// 使用上传文件Hook
|
||||
const { uploadFile } = useUploadFile();
|
||||
const [taskId, setTaskId] = useState<string>("");
|
||||
|
||||
/** 图片故事用例实例 */
|
||||
const imageStoryUseCase = useMemo(() => new ImageStoryUseCase(), []);
|
||||
@ -278,7 +279,7 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
|
||||
// 调用用例处理图片上传和分析
|
||||
const taskId = await imageStoryUseCase.handleImageUpload(activeImageUrl);
|
||||
|
||||
setTaskId(taskId);
|
||||
for await (const result of await imageStoryUseCase.pollTaskStatus(
|
||||
taskId
|
||||
)) {
|
||||
@ -465,8 +466,8 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
return {
|
||||
name: char.role_name,
|
||||
image_url: char.crop_url,
|
||||
character_analysis: JSON.parse(char.whisk_caption)
|
||||
.character_analysis,
|
||||
character_analysis: char.role_name+":"+JSON.parse(char.whisk_caption)
|
||||
?.character_analysis?.brief,
|
||||
};
|
||||
});
|
||||
|
||||
@ -479,6 +480,7 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
character_briefs,
|
||||
language,
|
||||
image_url: activeImageUrl,
|
||||
project_id:taskId
|
||||
};
|
||||
|
||||
// 调用create_movie_project_v2接口
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import { message } from "antd";
|
||||
import { StoryTemplateEntity } from "../domain/Entities";
|
||||
import { useUploadFile } from "../domain/service";
|
||||
import { debounce } from "lodash";
|
||||
import { TemplateStoryUseCase } from "../usecase/templateStoryUseCase";
|
||||
import { useState, useCallback, useMemo } from "react";
|
||||
import { createMovieProjectV3, generateTextToImage } from "@/api/movie_start";
|
||||
import { CreateMovieProjectV3Request } from "@/api/DTO/movie_start_dto";
|
||||
|
||||
/** 模板角色接口 */
|
||||
interface TemplateRole {
|
||||
@ -11,11 +16,6 @@ interface TemplateRole {
|
||||
/** 声音URL */
|
||||
voice_url: string;
|
||||
}
|
||||
import { TemplateStoryUseCase } from "../usecase/templateStoryUseCase";
|
||||
import { getUploadToken, uploadToQiniu } from "@/api/common";
|
||||
import { useState, useCallback, useMemo } from "react";
|
||||
import { createMovieProjectV3 } from "@/api/movie_start";
|
||||
import { CreateMovieProjectV3Request } from "@/api/DTO/movie_start_dto";
|
||||
|
||||
interface UseTemplateStoryService {
|
||||
/** 模板列表 */
|
||||
@ -42,9 +42,9 @@ interface UseTemplateStoryService {
|
||||
actionStory: (
|
||||
user_id: string,
|
||||
mode: "auto" | "manual",
|
||||
resolution: "720p" | "1080p" | "4k" ,
|
||||
resolution: "720p" | "1080p" | "4k",
|
||||
language: string
|
||||
) => Promise<string|undefined>;
|
||||
) => Promise<string | undefined>;
|
||||
/** 设置选中的模板 */
|
||||
setSelectedTemplate: (template: StoryTemplateEntity | null) => void;
|
||||
/** 设置活跃角色索引 */
|
||||
@ -55,7 +55,13 @@ interface UseTemplateStoryService {
|
||||
/**清空数据 */
|
||||
clearData: () => void;
|
||||
/** 上传人物头像并分析 */
|
||||
AvatarAndAnalyzeFeatures: (imageUrl: string) => Promise<void>;
|
||||
AvatarAndAnalyzeFeatures: (imageUrl: string, roleName?: string) => Promise<void>;
|
||||
/** 更新指定角色的图片 */
|
||||
updateRoleImage: (roleName: string, imageUrl: string) => void;
|
||||
/** 更新变量字段值 */
|
||||
updateFillableContentField: (fieldName: string, fieldValue: string) => void;
|
||||
/** 带防抖的失焦处理函数 */
|
||||
handleFieldBlur: (fieldName: string, fieldValue: string) => void;
|
||||
}
|
||||
|
||||
export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
||||
@ -96,7 +102,7 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
||||
setTemplateStoryList(templates);
|
||||
setSelectedTemplate(templates[0]);
|
||||
setActiveRoleIndex(0);
|
||||
console.log(selectedTemplate, activeRoleIndex)
|
||||
console.log(selectedTemplate, activeRoleIndex);
|
||||
} catch (err) {
|
||||
console.error("获取模板列表失败:", err);
|
||||
} finally {
|
||||
@ -115,7 +121,7 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
||||
* 设置当前活跃角色的图片URL
|
||||
*/
|
||||
const setActiveRoleData = useCallback(
|
||||
(imageUrl: string, desc: string): void => {
|
||||
(imageUrl: string): void => {
|
||||
if (
|
||||
!selectedTemplate ||
|
||||
activeRoleIndex < 0 ||
|
||||
@ -125,12 +131,11 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
||||
const character_brief = {
|
||||
name: selectedTemplate.storyRole[activeRoleIndex].role_name,
|
||||
image_url: imageUrl,
|
||||
character_analysis: JSON.parse(desc).character_analysis,
|
||||
};
|
||||
// const character_brief = {
|
||||
// name: selectedTemplate.storyRole[activeRoleIndex].role_name,
|
||||
// image_url: imageUrl,
|
||||
// character_analysis: JSON.parse(desc).character_analysis,
|
||||
// };
|
||||
const updatedTemplate = {
|
||||
...selectedTemplate,
|
||||
storyRole: selectedTemplate.storyRole.map((role, index) =>
|
||||
@ -138,7 +143,6 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
||||
? {
|
||||
...role,
|
||||
photo_url: imageUrl,
|
||||
role_description: character_brief,
|
||||
}
|
||||
: role
|
||||
),
|
||||
@ -177,22 +181,48 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
||||
[selectedTemplate, activeRoleIndex]
|
||||
);
|
||||
|
||||
/**
|
||||
* 更新变量字段值
|
||||
*/
|
||||
const updateFillableContentField = useCallback(
|
||||
(fieldName: string, fieldValue: string): void => {
|
||||
if (!selectedTemplate) return;
|
||||
|
||||
const updatedTemplate = {
|
||||
...selectedTemplate,
|
||||
fillable_content: selectedTemplate.fillable_content.map((field) =>
|
||||
field.field_name === fieldName
|
||||
? { ...field, field_value: fieldValue }
|
||||
: field
|
||||
),
|
||||
};
|
||||
setSelectedTemplate(updatedTemplate);
|
||||
},
|
||||
[selectedTemplate]
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* 上传人物头像并分析特征,替换旧的角色数据
|
||||
* @param {string} characterName - 角色名称
|
||||
* @param {string} imageUrl - 图片URL
|
||||
* @param {string} roleName - 角色名称(可选,如果不提供则使用当前活跃角色)
|
||||
*/
|
||||
const AvatarAndAnalyzeFeatures = useCallback(
|
||||
async (imageUrl: string): Promise<void> => {
|
||||
async (imageUrl: string, roleName?: string): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
// 调用用例处理人物头像上传和特征分析
|
||||
const result = await templateStoryUseCase.AvatarAndAnalyzeFeatures(
|
||||
imageUrl
|
||||
);
|
||||
// 如果提供了角色名称,更新指定角色;否则更新当前活跃角色
|
||||
if (roleName) {
|
||||
updateRoleImage(roleName, imageUrl);
|
||||
} else {
|
||||
setActiveRoleData(imageUrl);
|
||||
}
|
||||
|
||||
setActiveRoleData(result.crop_url, result.whisk_caption);
|
||||
console.log("人物头像和特征描述更新成功:", result);
|
||||
// 调用用例处理人物头像上传和特征分析
|
||||
// const result = await templateStoryUseCase.AvatarAndAnalyzeFeatures(
|
||||
// imageUrl
|
||||
// );
|
||||
} catch (error) {
|
||||
console.error("人物头像上传和特征分析失败:", error);
|
||||
throw error;
|
||||
@ -200,8 +230,64 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[templateStoryUseCase]
|
||||
[setActiveRoleData]
|
||||
);
|
||||
|
||||
/**
|
||||
* 更新指定角色的图片
|
||||
* @param {string} roleName - 角色名称
|
||||
* @param {string} imageUrl - 新的图片URL
|
||||
*/
|
||||
const updateRoleImage = useCallback(
|
||||
(roleName: string, imageUrl: string): void => {
|
||||
if (!selectedTemplate) return;
|
||||
|
||||
const updatedTemplate = {
|
||||
...selectedTemplate,
|
||||
storyRole: selectedTemplate.storyRole.map((role) =>
|
||||
role.role_name === roleName
|
||||
? { ...role, photo_url: imageUrl }
|
||||
: role
|
||||
),
|
||||
};
|
||||
|
||||
setSelectedTemplate(updatedTemplate);
|
||||
},
|
||||
[selectedTemplate]
|
||||
);
|
||||
/**
|
||||
* 带防抖的失焦处理函数
|
||||
* @param {string} fieldName - 字段名称
|
||||
* @param {string} fieldValue - 字段值
|
||||
*/
|
||||
const handleFieldBlur = useCallback(
|
||||
debounce(async (fieldName: string, fieldValue: string): Promise<void> => {
|
||||
try {
|
||||
// 设置 loading 状态
|
||||
setIsLoading(true);
|
||||
|
||||
// 调用图片生成接口
|
||||
const result = await generateTextToImage({
|
||||
description: fieldValue
|
||||
});
|
||||
|
||||
if (result.successful && result.data?.image_url) {
|
||||
// 更新对应角色的图片
|
||||
updateRoleImage(fieldName, result.data.image_url);
|
||||
console.log(`字段 ${fieldName} 图片生成成功:`, result.data.image_url);
|
||||
} else {
|
||||
console.error(`字段 ${fieldName} 图片生成失败:`, result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`字段 ${fieldName} 处理失败:`, error);
|
||||
} finally {
|
||||
// 清除 loading 状态
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, 500),
|
||||
[updateRoleImage, setIsLoading]
|
||||
);
|
||||
|
||||
const actionStory = useCallback(
|
||||
async (
|
||||
user_id: string,
|
||||
@ -209,20 +295,28 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
||||
resolution: "720p" | "1080p" | "4k" = "720p",
|
||||
language: string = "English"
|
||||
) => {
|
||||
console.log('selectedTemplate', selectedTemplate)
|
||||
try {
|
||||
const params: CreateMovieProjectV3Request = {
|
||||
user_id,
|
||||
mode,
|
||||
resolution,
|
||||
storyRole: selectedTemplate?.storyRole || [],
|
||||
language,
|
||||
template_id: selectedTemplate?.template_id || "",
|
||||
};
|
||||
// 设置 loading 状态
|
||||
setIsLoading(true);
|
||||
|
||||
const result = await createMovieProjectV3(params);
|
||||
return result.data.project_id as string;
|
||||
const params: CreateMovieProjectV3Request = {
|
||||
user_id,
|
||||
mode,
|
||||
resolution,
|
||||
storyRole: selectedTemplate?.storyRole || [],
|
||||
language,
|
||||
template_id: selectedTemplate?.template_id || "",
|
||||
fillable_content: selectedTemplate?.fillable_content || [],
|
||||
};
|
||||
console.log("params", params);
|
||||
const result = await createMovieProjectV3(params);
|
||||
return result.data.project_id as string;
|
||||
} catch (error) {
|
||||
console.error("创建电影项目失败:", error);
|
||||
} finally {
|
||||
// 清除 loading 状态
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[selectedTemplate]
|
||||
@ -239,6 +333,9 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => {
|
||||
setActiveRoleIndex: handleSetActiveRoleIndex,
|
||||
setActiveRoleAudio,
|
||||
AvatarAndAnalyzeFeatures,
|
||||
updateRoleImage,
|
||||
updateFillableContentField,
|
||||
handleFieldBlur,
|
||||
clearData: () => {
|
||||
setTemplateStoryList([]);
|
||||
setSelectedTemplate(null);
|
||||
|
||||
@ -88,7 +88,7 @@ export interface VideoSegmentEntity {
|
||||
video_status: number | null;
|
||||
}[];
|
||||
/**视频片段状态 0:视频加载中 1:任务已完成 2:任务失败 */
|
||||
status: number|null;
|
||||
status: number | null;
|
||||
/**镜头项 */
|
||||
lens: LensType[];
|
||||
}
|
||||
@ -152,9 +152,23 @@ export interface StoryTemplateEntity {
|
||||
image_url: string;
|
||||
character_analysis: Record<string, any>;
|
||||
};
|
||||
|
||||
/**照片URL */
|
||||
photo_url: string;
|
||||
/**声音URL */
|
||||
voice_url: string;
|
||||
}[];
|
||||
/**可填的内容 */
|
||||
fillable_content: {
|
||||
/** 字段名称 */
|
||||
field_name: string;
|
||||
/** 字段类型 */
|
||||
field_type: string;
|
||||
/** 字段值 */
|
||||
field_value?: string;
|
||||
/** 字段描述 */
|
||||
field_description?: string;
|
||||
/** 字段元数据 */
|
||||
field_meta?: Record<string, any>;
|
||||
}[];
|
||||
}
|
||||
|
||||
@ -18,14 +18,7 @@ import {
|
||||
Sparkles,
|
||||
Settings,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
Dropdown,
|
||||
Modal,
|
||||
Tooltip,
|
||||
Upload,
|
||||
Popconfirm,
|
||||
Image,
|
||||
} from "antd";
|
||||
import { Dropdown, Modal, Tooltip, Upload, Popconfirm, Image } from "antd";
|
||||
import { UploadOutlined } from "@ant-design/icons";
|
||||
import { StoryTemplateEntity } from "@/app/service/domain/Entities";
|
||||
import { useImageStoryServiceHook } from "@/app/service/Interaction/ImageStoryService";
|
||||
@ -72,6 +65,8 @@ const RenderTemplateStoryMode = ({
|
||||
setActiveRoleIndex,
|
||||
AvatarAndAnalyzeFeatures,
|
||||
setActiveRoleAudio,
|
||||
updateFillableContentField,
|
||||
handleFieldBlur,
|
||||
clearData,
|
||||
} = useTemplateStoryServiceHook();
|
||||
|
||||
@ -114,7 +109,12 @@ const RenderTemplateStoryMode = ({
|
||||
console.error("用户未登录");
|
||||
return;
|
||||
}
|
||||
const projectId = await actionStory(String(User.id), configOptions.mode, configOptions.resolution, configOptions.language);
|
||||
const projectId = await actionStory(
|
||||
String(User.id),
|
||||
configOptions.mode,
|
||||
configOptions.resolution,
|
||||
configOptions.language
|
||||
);
|
||||
if (projectId) {
|
||||
// 跳转到电影详情页
|
||||
router.push(`/create/work-flow?episodeId=${projectId}`);
|
||||
@ -204,9 +204,166 @@ const RenderTemplateStoryMode = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 变量字段填写区域 */}
|
||||
{selectedTemplate?.fillable_content &&
|
||||
selectedTemplate.fillable_content.length > 0 && (
|
||||
<div className="p-4 border-t border-white/10">
|
||||
<h3
|
||||
data-alt="variables-section-title"
|
||||
className="text-lg font-semibold text-white mb-4"
|
||||
>
|
||||
Template Configuration
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
{selectedTemplate.fillable_content.map((field, index) => (
|
||||
<div
|
||||
key={index}
|
||||
data-alt={`variable-field-${index}`}
|
||||
className="flex items-center gap-4"
|
||||
>
|
||||
{/* 字段名称 */}
|
||||
<div className="w-22 flex items-center gap-2">
|
||||
{/* 图片缩略图 - 显示对应角色的图片 */}
|
||||
<div className="relative group flex items-center justify-center">
|
||||
<Tooltip title={field.field_name} placement="top">
|
||||
<div
|
||||
data-alt={`field-thumbnail-${index}`}
|
||||
className="w-16 h-16 rounded-lg overflow-hidden border border-white/20 bg-white/[0.05] flex items-center justify-center cursor-pointer hover:scale-105 transition-all duration-200"
|
||||
>
|
||||
<Image
|
||||
src={
|
||||
selectedTemplate?.storyRole?.find(
|
||||
(role) => role.role_name === field.field_name
|
||||
)?.photo_url || "/assets/empty_video.png"
|
||||
}
|
||||
alt={field.field_name}
|
||||
className="w-full h-full object-cover"
|
||||
preview={{
|
||||
mask: null,
|
||||
maskClassName: "hidden",
|
||||
}}
|
||||
fallback="/assets/empty_video.png"
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{/* 上传按钮 - 右上角 */}
|
||||
<Upload
|
||||
name="fieldImage"
|
||||
showUploadList={false}
|
||||
beforeUpload={(file) => {
|
||||
// 验证文件类型
|
||||
const isImage = file.type.startsWith("image/");
|
||||
if (!isImage) {
|
||||
console.error("只能上传图片文件");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证文件大小 (5MB)
|
||||
const isLt5M = file.size / 1024 / 1024 < 5;
|
||||
if (!isLt5M) {
|
||||
console.error("图片大小不能超过5MB");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}}
|
||||
customRequest={async ({
|
||||
file,
|
||||
onSuccess,
|
||||
onError,
|
||||
}) => {
|
||||
try {
|
||||
const fileObj = file as File;
|
||||
console.log(
|
||||
"开始上传字段图片文件:",
|
||||
fileObj.name,
|
||||
fileObj.type,
|
||||
fileObj.size
|
||||
);
|
||||
|
||||
// 使用 hook 上传文件到七牛云
|
||||
const uploadedUrl = await uploadFile(
|
||||
fileObj,
|
||||
(progress) => {
|
||||
console.log(`上传进度: ${progress}%`);
|
||||
}
|
||||
);
|
||||
console.log(
|
||||
"字段图片上传成功,URL:",
|
||||
uploadedUrl
|
||||
);
|
||||
|
||||
// 调用 AvatarAndAnalyzeFeatures 更新对应角色的图片
|
||||
await AvatarAndAnalyzeFeatures(
|
||||
uploadedUrl,
|
||||
field.field_name
|
||||
);
|
||||
|
||||
onSuccess?.(uploadedUrl);
|
||||
} catch (error) {
|
||||
console.error("字段图片上传失败:", error);
|
||||
onError?.(error as Error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button
|
||||
data-alt={`field-upload-button-${index}`}
|
||||
className="absolute -top-1 -right-1 w-6 h-6 bg-blue-500 hover:bg-blue-600 text-white rounded-full flex items-center justify-center transition-all duration-200 opacity-0 group-hover:opacity-100 hover:scale-110 shadow-lg"
|
||||
title="change field image"
|
||||
>
|
||||
<UploadOutlined className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</Upload>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 输入框 */}
|
||||
<div className="flex-1 flex items-center relative">
|
||||
<input
|
||||
type="text"
|
||||
value={field.field_value || ""}
|
||||
onChange={(e) =>
|
||||
updateFillableContentField(
|
||||
field.field_name,
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
onBlur={(e) =>
|
||||
handleFieldBlur(field.field_name, e.target.value)
|
||||
}
|
||||
placeholder={`${field.field_description}`}
|
||||
className="w-full px-3 py-2 pr-10 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500/30 transition-all duration-200"
|
||||
/>
|
||||
|
||||
{/* 问号提示 - 直接放在输入框内部右侧 */}
|
||||
{field.field_description && (
|
||||
<Tooltip
|
||||
title={field.field_description}
|
||||
placement="top"
|
||||
classNames={{
|
||||
root: "max-w-xs",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
data-alt={`field-help-${index}`}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 w-5 h-5 rounded-full bg-white/10 border border-white/20 flex items-center justify-center cursor-help hover:bg-white/20 transition-colors duration-200"
|
||||
>
|
||||
<span className="text-white text-xs font-bold">
|
||||
?
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 角色自定义部分 - 精简布局 */}
|
||||
<div className="p-4">
|
||||
{/* <div className="p-4">
|
||||
<h3
|
||||
data-alt="roles-section-title"
|
||||
className="text-lg font-semibold text-white mb-4"
|
||||
@ -215,11 +372,11 @@ const RenderTemplateStoryMode = ({
|
||||
</h3>
|
||||
|
||||
{/* 紧凑布局 */}
|
||||
<div className="mb-6 flex gap-4">
|
||||
{/* <div className="mb-6 flex gap-4">
|
||||
{/* 左侧:音频部分 */}
|
||||
<div className="flex-1 space-y-4">
|
||||
{/* <div className="flex-1 space-y-4">
|
||||
{/* 音频操作区域 - 使用新的 AudioRecorder 组件 */}
|
||||
<div className="">
|
||||
{/* <div className="">
|
||||
<AudioRecorder
|
||||
audioUrl={activeRole?.voice_url || ""}
|
||||
onAudioRecorded={(audioBlob, audioUrl) => {
|
||||
@ -233,7 +390,7 @@ const RenderTemplateStoryMode = ({
|
||||
</div>
|
||||
|
||||
{/* 右侧:角色图片缩略图列表 - 精简 */}
|
||||
<div className="w-24 flex flex-col gap-y-[.6rem] ">
|
||||
{/* <div className="w-24 flex flex-col gap-y-[.6rem] ">
|
||||
{selectedTemplate.storyRole.map((role, index: number) => (
|
||||
<div key={index} className="relative group">
|
||||
<Tooltip title={role.role_name} placement="left">
|
||||
@ -260,7 +417,7 @@ const RenderTemplateStoryMode = ({
|
||||
</Tooltip>
|
||||
|
||||
{/* 上传按钮 - 右上角 */}
|
||||
<Tooltip title="更换角色头像" placement="top">
|
||||
{/* <Tooltip title="更换角色头像" placement="top">
|
||||
<Upload
|
||||
name="avatar"
|
||||
showUploadList={false}
|
||||
@ -322,8 +479,8 @@ const RenderTemplateStoryMode = ({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className=" absolute -bottom-4 right-0">
|
||||
</div> */}
|
||||
<div className=" absolute -bottom-8 right-0">
|
||||
<ActionButton
|
||||
isCreating={localLoading > 0}
|
||||
handleCreateVideo={handleConfirm}
|
||||
@ -371,7 +528,7 @@ const RenderTemplateStoryMode = ({
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 pb-4 ">
|
||||
<div className="flex gap-4 pb-8 ">
|
||||
{templateListRender()}
|
||||
<div className="flex-1">{storyEditorRender()}</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user