forked from 77media/video-flow
添加角色简介生成功能,移除生成剧本逻辑,优化模板故事模式样式
This commit is contained in:
parent
84b6662a51
commit
d7d41e91ac
@ -1105,6 +1105,37 @@ export const analyzeImageDescription = async (request: {
|
|||||||
}>>("/character/analyze_image_description", request);
|
}>>("/character/analyze_image_description", request);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成角色简介
|
||||||
|
* @description 接收图片URL,调用AI服务分析图片并生成角色简介
|
||||||
|
* @param request - 角色简介生成请求参数
|
||||||
|
* @returns Promise<ApiResponse<角色简介结果>>
|
||||||
|
*/
|
||||||
|
export const generateCharacterBrief = async (request: {
|
||||||
|
/** 图片的URL地址,必须是有效的HTTP/HTTPS链接 */
|
||||||
|
image_url: string;
|
||||||
|
}) => {
|
||||||
|
return post<ApiResponse<{
|
||||||
|
success: boolean;
|
||||||
|
/** 原始图片URL */
|
||||||
|
original_url: string;
|
||||||
|
/** 裁剪后图片URL */
|
||||||
|
cutout_url: string;
|
||||||
|
/** 头像框选区域 */
|
||||||
|
bounding_box: {
|
||||||
|
/** 左侧坐标 */
|
||||||
|
left: number;
|
||||||
|
/** 顶部坐标 */
|
||||||
|
top: number;
|
||||||
|
/** 宽度 */
|
||||||
|
width: number;
|
||||||
|
/** 高度 */
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
character_brief:Record<string, any>
|
||||||
|
}>>("/movie_story/generate_character_brief", request);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查分镜视频状态
|
* 检查分镜视频状态
|
||||||
* @description 保存当前项目数据
|
* @description 保存当前项目数据
|
||||||
|
|||||||
@ -37,8 +37,6 @@ interface UseImageStoryService {
|
|||||||
uploadAndAnalyzeImage: () => Promise<void>;
|
uploadAndAnalyzeImage: () => Promise<void>;
|
||||||
/** 触发文件选择 */
|
/** 触发文件选择 */
|
||||||
triggerFileSelection: () => Promise<void>;
|
triggerFileSelection: () => Promise<void>;
|
||||||
/** 触发生成剧本函数 */
|
|
||||||
generateScript: () => Promise<string>;
|
|
||||||
/** 更新故事类型 */
|
/** 更新故事类型 */
|
||||||
updateStoryType: (storyType: string) => void;
|
updateStoryType: (storyType: string) => void;
|
||||||
/** 更新故事内容 */
|
/** 更新故事内容 */
|
||||||
@ -278,39 +276,6 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
|||||||
}
|
}
|
||||||
}, [imageStoryUseCase, activeImageUrl, storyContent]);
|
}, [imageStoryUseCase, activeImageUrl, storyContent]);
|
||||||
|
|
||||||
/**
|
|
||||||
* 触发生成剧本函数
|
|
||||||
* @returns {Promise<string>} 生成的剧本ID或内容
|
|
||||||
*/
|
|
||||||
const generateScript = useCallback(async (): Promise<string> => {
|
|
||||||
if (!activeImageUrl) {
|
|
||||||
throw new Error("请先上传图片");
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalStoryContent = storyContent;
|
|
||||||
if (!finalStoryContent.trim()) {
|
|
||||||
throw new Error("请先输入或生成故事内容");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
// 这里可以调用后端API生成剧本
|
|
||||||
// 暂时返回一个模拟的剧本ID
|
|
||||||
const scriptId = `script_${Date.now()}`;
|
|
||||||
|
|
||||||
// TODO: 实现实际的剧本生成逻辑
|
|
||||||
// const response = await generateScriptFromImage(imageStory);
|
|
||||||
// return response.scriptId;
|
|
||||||
|
|
||||||
return scriptId;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("生成剧本失败:", error);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
}, [activeImageUrl, storyContent]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新故事类型
|
* 更新故事类型
|
||||||
@ -531,7 +496,6 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
|||||||
setCharactersAnalysis,
|
setCharactersAnalysis,
|
||||||
uploadAndAnalyzeImage,
|
uploadAndAnalyzeImage,
|
||||||
triggerFileSelection,
|
triggerFileSelection,
|
||||||
generateScript,
|
|
||||||
updateStoryType,
|
updateStoryType,
|
||||||
updateStoryContent,
|
updateStoryContent,
|
||||||
updateCharacterName,
|
updateCharacterName,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { ImageStoryEntity } from "../domain/Entities";
|
import { ImageStoryEntity } from "../domain/Entities";
|
||||||
import { AIGenerateImageStory } from "@/api/movie_start";
|
import { AIGenerateImageStory } from "@/api/movie_start";
|
||||||
import { MovieStartDTO, CharacterAnalysis } from "@/api/DTO/movie_start_dto";
|
import { MovieStartDTO, CharacterAnalysis } from "@/api/DTO/movie_start_dto";
|
||||||
import { analyzeImageDescription } from "@/api/video_flow";
|
import { generateCharacterBrief } from "@/api/video_flow";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图片故事用例
|
* 图片故事用例
|
||||||
@ -101,6 +101,12 @@ export class ImageStoryUseCase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.successful && response.data) {
|
if (response.successful && response.data) {
|
||||||
|
// ! 后端实际返回的是对象 但是由于前端只是做字符串数据的转交,所以这里就处理成字符串
|
||||||
|
// ! 至于为什么这里是前端来处理,因为后端这个数据,很多时候都说要以对象方式使用,唯独给AI时,是字符串
|
||||||
|
// ! 然后后端就不处理这个东西了,就给前端来处理了,真 懒
|
||||||
|
response.data.characters_analysis.forEach((character) => {
|
||||||
|
character.whisk_caption = JSON.stringify(character.whisk_caption);
|
||||||
|
});
|
||||||
// 解析并存储新的数据结构
|
// 解析并存储新的数据结构
|
||||||
this.parseAndStoreAnalysisData(response.data);
|
this.parseAndStoreAnalysisData(response.data);
|
||||||
|
|
||||||
@ -254,7 +260,7 @@ export class ImageStoryUseCase {
|
|||||||
const imageUrl = await uploadFile(file);
|
const imageUrl = await uploadFile(file);
|
||||||
|
|
||||||
// 2. 调用AI分析接口获取人物特征描述
|
// 2. 调用AI分析接口获取人物特征描述
|
||||||
const analysisResult = await analyzeImageDescription({
|
const analysisResult = await generateCharacterBrief({
|
||||||
image_url: imageUrl,
|
image_url: imageUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -265,7 +271,7 @@ export class ImageStoryUseCase {
|
|||||||
// 3. 返回新的头像URL和特征描述,用于替换旧数据
|
// 3. 返回新的头像URL和特征描述,用于替换旧数据
|
||||||
const result = {
|
const result = {
|
||||||
crop_url: imageUrl,
|
crop_url: imageUrl,
|
||||||
whisk_caption: analysisResult.data.description,
|
whisk_caption: JSON.stringify(analysisResult.data.character_brief),
|
||||||
};
|
};
|
||||||
|
|
||||||
// 清理临时元素
|
// 清理临时元素
|
||||||
|
|||||||
@ -142,7 +142,7 @@ const RenderTemplateStoryMode = ({
|
|||||||
) : selectedTemplate ? (
|
) : selectedTemplate ? (
|
||||||
<div className="relative h-full">
|
<div className="relative h-full">
|
||||||
{/* 模板信息头部 - 增加顶部空间 */}
|
{/* 模板信息头部 - 增加顶部空间 */}
|
||||||
<div className="flex gap-3 py-4 border-b border-white/[0.1] min-h-[300px]">
|
<div className="flex gap-3 py-4 border-b border-white/[0.1] h-[300px]">
|
||||||
{/* 左侧图片 */}
|
{/* 左侧图片 */}
|
||||||
<div className="w-1/3">
|
<div className="w-1/3">
|
||||||
<div
|
<div
|
||||||
@ -191,7 +191,7 @@ const RenderTemplateStoryMode = ({
|
|||||||
{/* 左侧:当前选中角色的音频与照片更改 - 精简版本 */}
|
{/* 左侧:当前选中角色的音频与照片更改 - 精简版本 */}
|
||||||
<div className="flex-1 space-y-4">
|
<div className="flex-1 space-y-4">
|
||||||
{/* 图片上传部分 - 精简 */}
|
{/* 图片上传部分 - 精简 */}
|
||||||
<div className="space-y-2 mb-8">
|
<div className="space-y-2 mb-8 mt-4">
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title="Upload a portrait photo to replace this character's appearance in the movie."
|
title="Upload a portrait photo to replace this character's appearance in the movie."
|
||||||
@ -350,7 +350,7 @@ const RenderTemplateStoryMode = ({
|
|||||||
}}
|
}}
|
||||||
footer={null}
|
footer={null}
|
||||||
width="60%"
|
width="60%"
|
||||||
style={{ maxWidth: "800px", marginTop: "10vh" }}
|
style={{ maxWidth: "800px", marginTop: "0vh" }}
|
||||||
className="template-modal"
|
className="template-modal"
|
||||||
closeIcon={
|
closeIcon={
|
||||||
<div className="w-6 h-6 bg-white/10 rounded-full flex items-center justify-center hover:bg-white/20 transition-colors">
|
<div className="w-6 h-6 bg-white/10 rounded-full flex items-center justify-center hover:bg-white/20 transition-colors">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user