forked from 77media/video-flow
新的阶段
This commit is contained in:
parent
e405f4bd7d
commit
85687f5840
@ -45,6 +45,41 @@ export interface MovieStartDTO {
|
||||
/** 错误信息 */
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
/** 电影故事任务详情 */
|
||||
export interface MovieStoryTaskDetail {
|
||||
/** 任务ID */
|
||||
task_id: string;
|
||||
/** 状态 */
|
||||
status: string;
|
||||
/** 进度 */
|
||||
progress: number;
|
||||
/** 当前步骤 */
|
||||
current_step: string;
|
||||
/** 步骤消息 */
|
||||
step_message: string;
|
||||
/** 已用时间 */
|
||||
elapsed_time: number;
|
||||
/** 预计剩余时间 */
|
||||
estimated_remaining: number | null;
|
||||
/** 错误信息 */
|
||||
error_message: string | null;
|
||||
/** 结果 */
|
||||
result: MovieStartDTO;
|
||||
}
|
||||
|
||||
/**图片分析出故事的任务相关数据,用于轮询查状态 */
|
||||
export interface StoryAnalysisTask{
|
||||
/** 任务ID */
|
||||
task_id:string;
|
||||
/** 状态 */
|
||||
status:string;
|
||||
/** 消息 */
|
||||
message:string;
|
||||
/** 预计时长 */
|
||||
estimated_duration:number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建电影项目V2请求参数 照片生成电影
|
||||
*/
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
import { ApiResponse } from "./common";
|
||||
import { CreateMovieProjectV2Request, CreateMovieProjectResponse, MovieStartDTO } from "./DTO/movie_start_dto";
|
||||
import {
|
||||
CreateMovieProjectV2Request,
|
||||
CreateMovieProjectResponse,
|
||||
MovieStartDTO,
|
||||
StoryAnalysisTask,
|
||||
MovieStoryTaskDetail,
|
||||
} from "./DTO/movie_start_dto";
|
||||
import { get, post } from "./request";
|
||||
import {
|
||||
StoryTemplateEntity,
|
||||
@ -30,7 +36,7 @@ export const AIGenerateImageStory = async (request: {
|
||||
image_url: string;
|
||||
user_text: string;
|
||||
}) => {
|
||||
return await post<ApiResponse<MovieStartDTO>>(
|
||||
return await post<ApiResponse<StoryAnalysisTask>>(
|
||||
"/movie_story/generate",
|
||||
request
|
||||
);
|
||||
@ -63,3 +69,14 @@ export const createMovieProjectV3 = async (
|
||||
request
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取电影故事任务详情
|
||||
* @param taskId - 任务ID
|
||||
* @returns Promise<ApiResponse<MovieStoryTaskDetail>>
|
||||
*/
|
||||
export const getMovieStoryTask = async (taskId: string) => {
|
||||
return await get<ApiResponse<MovieStoryTaskDetail>>(
|
||||
`/movie_story/task/${taskId}`
|
||||
);
|
||||
};
|
||||
|
||||
@ -8,7 +8,11 @@ import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
} from "react";
|
||||
import { CharacterAnalysis, CreateMovieProjectV2Request, CreateMovieProjectResponse } from "@/api/DTO/movie_start_dto";
|
||||
import {
|
||||
CharacterAnalysis,
|
||||
CreateMovieProjectV2Request,
|
||||
CreateMovieProjectResponse,
|
||||
} from "@/api/DTO/movie_start_dto";
|
||||
import { createMovieProjectV2 } from "@/api/movie_start";
|
||||
|
||||
interface UseImageStoryService {
|
||||
@ -32,6 +36,8 @@ interface UseImageStoryService {
|
||||
avatarComputed: Array<{ name: string; url: string }>;
|
||||
/** 原始用户描述 */
|
||||
originalUserDescription: string;
|
||||
/** 分析任务进度 */
|
||||
taskProgress: number;
|
||||
/** 上传图片并分析 */
|
||||
uploadAndAnalyzeImage: () => Promise<void>;
|
||||
/** 触发文件选择 */
|
||||
@ -52,13 +58,15 @@ interface UseImageStoryService {
|
||||
mode?: "auto" | "manual",
|
||||
resolution?: "720p" | "1080p" | "4k",
|
||||
language?: string
|
||||
) => Promise<CreateMovieProjectResponse|undefined>;
|
||||
) => Promise<CreateMovieProjectResponse | undefined>;
|
||||
/** 设置角色分析 */
|
||||
setCharactersAnalysis: Dispatch<SetStateAction<CharacterAnalysis[]>>;
|
||||
/** 设置原始用户描述 */
|
||||
setOriginalUserDescription: Dispatch<SetStateAction<string>>;
|
||||
/** 上传人物头像并分析特征,替换旧的角色数据 */
|
||||
uploadCharacterAvatarAndAnalyzeFeatures: (characterName: string) => Promise<void>;
|
||||
uploadCharacterAvatarAndAnalyzeFeatures: (
|
||||
characterName: string
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
@ -91,7 +99,8 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
// 流程状态
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [hasAnalyzed, setHasAnalyzed] = useState(false);
|
||||
|
||||
/** 分析任务进度 */
|
||||
const [taskProgress, setTaskProgress] = useState(0);
|
||||
// 使用上传文件Hook
|
||||
const { uploadFile } = useUploadFile();
|
||||
|
||||
@ -217,24 +226,26 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
return [];
|
||||
}
|
||||
|
||||
return charactersAnalysis.map((character) => {
|
||||
console.log('character', character)
|
||||
// 如果已经有头像URL,直接返回
|
||||
if (character.crop_url) {
|
||||
return {
|
||||
name: character.role_name,
|
||||
url: character.crop_url,
|
||||
};
|
||||
}
|
||||
return charactersAnalysis
|
||||
.map((character) => {
|
||||
console.log("character", character);
|
||||
// 如果已经有头像URL,直接返回
|
||||
if (character.crop_url) {
|
||||
return {
|
||||
name: character.role_name,
|
||||
url: character.crop_url,
|
||||
};
|
||||
}
|
||||
|
||||
// // 异步生成头像URL
|
||||
// generateAvatarFromRegion(character, activeImageUrl);
|
||||
// // 异步生成头像URL
|
||||
// generateAvatarFromRegion(character, activeImageUrl);
|
||||
|
||||
// return {
|
||||
// name: character.role_name,
|
||||
// url: "", // 初始为空,异步生成完成后会更新
|
||||
// };
|
||||
}).filter(Boolean) as { name: string; url: string }[];
|
||||
// return {
|
||||
// name: character.role_name,
|
||||
// url: "", // 初始为空,异步生成完成后会更新
|
||||
// };
|
||||
})
|
||||
.filter(Boolean) as { name: string; url: string }[];
|
||||
}, [charactersAnalysis, activeImageUrl, generateAvatarFromRegion]);
|
||||
/**
|
||||
* 上传图片并分析
|
||||
@ -243,28 +254,54 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
const uploadAndAnalyzeImage = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setTaskProgress(1);
|
||||
|
||||
const setData = () => {
|
||||
setOriginalUserDescription(storyContent);
|
||||
// 获取更新后的数据
|
||||
const updatedStory = imageStoryUseCase.storyLogline;
|
||||
const updatedCharacters = imageStoryUseCase.charactersAnalysis;
|
||||
const updatedGenres = imageStoryUseCase.potentialGenres;
|
||||
const updatedImageStory = imageStoryUseCase.imageStory;
|
||||
setSelectedCategory(imageStoryUseCase.potentialGenres[0]);
|
||||
// 更新所有响应式状态
|
||||
setCharactersAnalysis(updatedCharacters);
|
||||
setPotentialGenres(updatedGenres);
|
||||
setImageStory(updatedImageStory);
|
||||
|
||||
// 将AI分析的故事内容直接更新到统一的故事内容字段
|
||||
updateStoryContent(updatedStory || "");
|
||||
|
||||
// 标记已分析
|
||||
setHasAnalyzed(true);
|
||||
};
|
||||
|
||||
// 调用用例处理图片上传和分析
|
||||
const newImageStory = await imageStoryUseCase.handleImageUpload(
|
||||
activeImageUrl
|
||||
);
|
||||
setOriginalUserDescription(storyContent);
|
||||
// 获取更新后的数据
|
||||
const updatedStory = imageStoryUseCase.storyLogline;
|
||||
const updatedCharacters = imageStoryUseCase.charactersAnalysis;
|
||||
const updatedGenres = imageStoryUseCase.potentialGenres;
|
||||
const updatedImageStory = imageStoryUseCase.imageStory;
|
||||
setSelectedCategory(imageStoryUseCase.potentialGenres[0]);
|
||||
// 更新所有响应式状态
|
||||
setCharactersAnalysis(updatedCharacters);
|
||||
setPotentialGenres(updatedGenres);
|
||||
setImageStory(updatedImageStory);
|
||||
const taskId = await imageStoryUseCase.handleImageUpload(activeImageUrl);
|
||||
|
||||
// 将AI分析的故事内容直接更新到统一的故事内容字段
|
||||
updateStoryContent(updatedStory || "");
|
||||
|
||||
// 标记已分析
|
||||
setHasAnalyzed(true);
|
||||
for await (const result of await imageStoryUseCase.pollTaskStatus(
|
||||
taskId
|
||||
)) {
|
||||
setTaskProgress(result.progress);
|
||||
switch (result.status) {
|
||||
case "submitted":
|
||||
break;
|
||||
case "processing":
|
||||
setData();
|
||||
break;
|
||||
case "completed":
|
||||
setData();
|
||||
setHasAnalyzed(true);
|
||||
setTaskProgress(0);
|
||||
return
|
||||
case "failed":
|
||||
setHasAnalyzed(false);
|
||||
setTaskProgress(0);
|
||||
return
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("图片上传分析失败:", error);
|
||||
setHasAnalyzed(false);
|
||||
@ -275,7 +312,6 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
}
|
||||
}, [imageStoryUseCase, activeImageUrl, storyContent]);
|
||||
|
||||
|
||||
/**
|
||||
* 更新故事类型
|
||||
* @param {string} storyType - 新的故事类型
|
||||
@ -415,56 +451,67 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
});
|
||||
}, [uploadFile]);
|
||||
|
||||
const actionMovie = useCallback(async (
|
||||
user_id: string,
|
||||
mode: "auto" | "manual" = "auto",
|
||||
resolution: "720p" | "1080p" | "4k" = "720p",
|
||||
language: string = "English"
|
||||
) => {
|
||||
try {
|
||||
if (hasAnalyzed) {
|
||||
// 从charactersAnalysis中提取whisk_caption字段组成数组
|
||||
const character_briefs = charactersAnalysis.map(char => {
|
||||
const actionMovie = useCallback(
|
||||
async (
|
||||
user_id: string,
|
||||
mode: "auto" | "manual" = "auto",
|
||||
resolution: "720p" | "1080p" | "4k" = "720p",
|
||||
language: string = "English"
|
||||
) => {
|
||||
try {
|
||||
if (hasAnalyzed) {
|
||||
// 从charactersAnalysis中提取whisk_caption字段组成数组
|
||||
const character_briefs = charactersAnalysis.map((char) => {
|
||||
return {
|
||||
name: char.role_name,
|
||||
image_url: char.crop_url,
|
||||
character_analysis: JSON.parse(char.whisk_caption)
|
||||
.character_analysis,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
name:char.role_name,
|
||||
image_url:char.crop_url,
|
||||
character_analysis:JSON.parse(char.whisk_caption).character_analysis
|
||||
}
|
||||
});
|
||||
const params: CreateMovieProjectV2Request = {
|
||||
script: storyContent,
|
||||
user_id,
|
||||
mode,
|
||||
resolution,
|
||||
genre: selectedCategory,
|
||||
character_briefs,
|
||||
language,
|
||||
image_url: activeImageUrl,
|
||||
};
|
||||
|
||||
const params: CreateMovieProjectV2Request = {
|
||||
script: storyContent,
|
||||
user_id,
|
||||
mode,
|
||||
resolution,
|
||||
genre: selectedCategory,
|
||||
character_briefs,
|
||||
language,
|
||||
image_url: activeImageUrl,
|
||||
};
|
||||
|
||||
// 调用create_movie_project_v2接口
|
||||
const result = await createMovieProjectV2(params)
|
||||
return result.data;
|
||||
// 调用create_movie_project_v2接口
|
||||
const result = await createMovieProjectV2(params);
|
||||
return result.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("创建电影项目失败:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("创建电影项目失败:", error);
|
||||
}
|
||||
}, [hasAnalyzed, storyContent, charactersAnalysis, selectedCategory, activeImageUrl]);
|
||||
},
|
||||
[
|
||||
hasAnalyzed,
|
||||
storyContent,
|
||||
charactersAnalysis,
|
||||
selectedCategory,
|
||||
activeImageUrl,
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* 上传人物头像并分析特征,替换旧的角色数据
|
||||
* @param {string} characterName - 角色名称
|
||||
*/
|
||||
const uploadCharacterAvatarAndAnalyzeFeatures = useCallback(async (characterName: string): Promise<void> => {
|
||||
/**
|
||||
* 上传人物头像并分析特征,替换旧的角色数据
|
||||
* @param {string} characterName - 角色名称
|
||||
*/
|
||||
const uploadCharacterAvatarAndAnalyzeFeatures = useCallback(
|
||||
async (characterName: string): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
// 调用用例处理人物头像上传和特征分析
|
||||
const result = await imageStoryUseCase.uploadCharacterAvatarAndAnalyzeFeatures(
|
||||
uploadFile
|
||||
);
|
||||
const result =
|
||||
await imageStoryUseCase.uploadCharacterAvatarAndAnalyzeFeatures(
|
||||
uploadFile
|
||||
);
|
||||
|
||||
// 用新的头像和特征描述替换旧的角色数据
|
||||
setCharactersAnalysis((prev) =>
|
||||
@ -473,7 +520,7 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
? {
|
||||
...char,
|
||||
crop_url: result.crop_url,
|
||||
whisk_caption: result.whisk_caption
|
||||
whisk_caption: result.whisk_caption,
|
||||
}
|
||||
: char
|
||||
)
|
||||
@ -486,7 +533,9 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [imageStoryUseCase, uploadFile]);
|
||||
},
|
||||
[imageStoryUseCase, uploadFile]
|
||||
);
|
||||
|
||||
return {
|
||||
imageStory,
|
||||
@ -499,6 +548,7 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
||||
hasAnalyzed,
|
||||
avatarComputed,
|
||||
originalUserDescription,
|
||||
taskProgress,
|
||||
setCharactersAnalysis,
|
||||
uploadAndAnalyzeImage,
|
||||
triggerFileSelection,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ImageStoryEntity } from "../domain/Entities";
|
||||
import { AIGenerateImageStory } from "@/api/movie_start";
|
||||
import { AIGenerateImageStory, getMovieStoryTask } from "@/api/movie_start";
|
||||
import { MovieStartDTO, CharacterAnalysis } from "@/api/DTO/movie_start_dto";
|
||||
import { generateCharacterBrief } from "@/api/video_flow";
|
||||
|
||||
@ -32,6 +32,7 @@ export class ImageStoryUseCase {
|
||||
|
||||
/** 是否正在上传 */
|
||||
isUploading: boolean = false;
|
||||
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
@ -88,10 +89,9 @@ export class ImageStoryUseCase {
|
||||
|
||||
/**
|
||||
* 使用AI分析图片
|
||||
* @returns {Promise<void>}
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async analyzeImageWithAI() {
|
||||
console.log("this.imageStory.imageUrl", this.imageStory.imageUrl);
|
||||
try {
|
||||
//
|
||||
// 调用AI分析接口
|
||||
@ -100,27 +100,53 @@ export class ImageStoryUseCase {
|
||||
user_text: this.imageStory.imageStory || "",
|
||||
});
|
||||
|
||||
if (response.successful && response.data) {
|
||||
// ! 后端实际返回的是对象 但是由于前端只是做字符串数据的转交,所以这里就处理成字符串
|
||||
// ! 至于为什么这里是前端来处理,因为后端这个数据,很多时候都说要以对象方式使用,唯独给AI时,是字符串
|
||||
// ! 然后后端就不处理这个东西了,就给前端来处理了,真 懒
|
||||
response.data.characters_analysis.forEach((character) => {
|
||||
character.whisk_caption = JSON.stringify(character.whisk_caption);
|
||||
});
|
||||
// 解析并存储新的数据结构
|
||||
this.parseAndStoreAnalysisData(response.data);
|
||||
|
||||
// 组合成ImageStoryEntity
|
||||
this.composeImageStoryEntity(response.data);
|
||||
return this.imageStory;
|
||||
} else {
|
||||
throw new Error("AI分析失败");
|
||||
}
|
||||
return response.data.task_id;
|
||||
} catch (error) {
|
||||
console.error("AI分析失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 轮询查状态
|
||||
* @param taskId - 任务ID
|
||||
* @param interval - 轮询间隔时间
|
||||
*/
|
||||
async pollTaskStatus(taskId: string, interval: number = 1000) {
|
||||
// 好老套方案,但是有效
|
||||
let self = this;
|
||||
return {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
while (true) {
|
||||
|
||||
const response = await getMovieStoryTask(taskId);
|
||||
console.log("taskId", taskId,response);
|
||||
if (response.successful && response.data) {
|
||||
if (response.data.result) {
|
||||
response.data.result.characters_analysis?.forEach((character) => {
|
||||
character.whisk_caption = JSON.stringify(
|
||||
character.whisk_caption
|
||||
);
|
||||
});
|
||||
// 解析并存储新的数据结构
|
||||
self.parseAndStoreAnalysisData(response.data.result);
|
||||
|
||||
// 组合成ImageStoryEntity
|
||||
self.composeImageStoryEntity(response.data.result);
|
||||
}
|
||||
|
||||
yield {
|
||||
status: response.data.status,
|
||||
imageStory: self.imageStory,
|
||||
progress: response.data.progress,
|
||||
};
|
||||
} else {
|
||||
throw new Error("AI分析失败");
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, interval));
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析并存储分析数据到类属性中
|
||||
@ -159,7 +185,7 @@ export class ImageStoryUseCase {
|
||||
this.setImageStory({
|
||||
...this.imageStory,
|
||||
imageAnalysis: data.story_logline || "",
|
||||
storyType: data.potential_genres[0] || "", // 使用第一个分类作为故事类型
|
||||
storyType: data.potential_genres?.[0] || "", // 使用第一个分类作为故事类型
|
||||
roleImage,
|
||||
});
|
||||
}
|
||||
@ -271,7 +297,9 @@ export class ImageStoryUseCase {
|
||||
// 3. 返回新的头像URL和特征描述,用于替换旧数据
|
||||
const result = {
|
||||
crop_url: imageUrl,
|
||||
whisk_caption: JSON.stringify(analysisResult.data.character_brief),
|
||||
whisk_caption: JSON.stringify(
|
||||
analysisResult.data.character_brief
|
||||
),
|
||||
};
|
||||
|
||||
// 清理临时元素
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
@ -819,6 +819,7 @@ const PhotoStoryModal = ({
|
||||
selectedCategory,
|
||||
isLoading,
|
||||
hasAnalyzed,
|
||||
taskProgress,
|
||||
updateStoryType,
|
||||
updateStoryContent,
|
||||
updateCharacterName,
|
||||
@ -839,6 +840,11 @@ const PhotoStoryModal = ({
|
||||
onClose();
|
||||
};
|
||||
const router = useRouter();
|
||||
const taskProgressRef = useRef(taskProgress);
|
||||
|
||||
useEffect(() => {
|
||||
taskProgressRef.current = taskProgress;
|
||||
}, [taskProgress]);
|
||||
// 处理图片上传
|
||||
const handleImageUpload = async (e: any) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
@ -889,9 +895,10 @@ const PhotoStoryModal = ({
|
||||
let timeout = 100;
|
||||
let timer: NodeJS.Timeout;
|
||||
timer = setInterval(() => {
|
||||
const currentProgress = taskProgressRef.current;
|
||||
setLocalLoading((prev) => {
|
||||
if (prev >= 95) {
|
||||
return 95;
|
||||
if (prev >= currentProgress && currentProgress != 0) {
|
||||
return currentProgress;
|
||||
}
|
||||
return prev + 0.1;
|
||||
});
|
||||
@ -899,17 +906,8 @@ const PhotoStoryModal = ({
|
||||
try {
|
||||
await uploadAndAnalyzeImage();
|
||||
} finally {
|
||||
timeout = 10;
|
||||
clearInterval(timer);
|
||||
timer = setInterval(() => {
|
||||
setLocalLoading((prev) => {
|
||||
if (prev >= 100) {
|
||||
clearInterval(timer);
|
||||
return 0;
|
||||
}
|
||||
return prev + 1;
|
||||
});
|
||||
}, timeout);
|
||||
setLocalLoading(0);
|
||||
}
|
||||
};
|
||||
|
||||
@ -942,22 +940,22 @@ const PhotoStoryModal = ({
|
||||
<div className="flex items-start gap-4">
|
||||
{/* 左侧:图片上传 */}
|
||||
<div className="flex-shrink-0">
|
||||
<div
|
||||
data-alt="image-upload-area"
|
||||
className={`w-32 h-32 rounded-lg flex flex-col items-center justify-center transition-all duration-300 cursor-pointer ${
|
||||
activeImageUrl
|
||||
? "border-2 border-white/20 bg-white/[0.05]"
|
||||
: "border-2 border-dashed border-white/20 bg-white/[0.02] hover:border-white/40 hover:bg-white/[0.05] hover:scale-105"
|
||||
}`}
|
||||
<div
|
||||
data-alt="image-upload-area"
|
||||
className={`w-32 h-32 rounded-lg flex flex-col items-center justify-center transition-all duration-300 cursor-pointer ${
|
||||
activeImageUrl
|
||||
? "border-2 border-white/20 bg-white/[0.05]"
|
||||
: "border-2 border-dashed border-white/20 bg-white/[0.02] hover:border-white/40 hover:bg-white/[0.05] hover:scale-105"
|
||||
}`}
|
||||
onClick={handleImageUpload}
|
||||
>
|
||||
{activeImageUrl ? (
|
||||
<div className="relative w-full h-full">
|
||||
<img
|
||||
src={activeImageUrl}
|
||||
alt="Story inspiration"
|
||||
className="w-full h-full object-cover rounded-lg bg-white/[0.05]"
|
||||
/>
|
||||
<img
|
||||
src={activeImageUrl}
|
||||
alt="Story inspiration"
|
||||
className="w-full h-full object-cover rounded-lg bg-white/[0.05]"
|
||||
/>
|
||||
<Popconfirm
|
||||
title="Clear all content"
|
||||
description="Are you sure you want to clear all content? This action cannot be undone."
|
||||
@ -1003,17 +1001,17 @@ const PhotoStoryModal = ({
|
||||
key={`${avatar.name}-${index}`}
|
||||
className="flex flex-col items-center"
|
||||
>
|
||||
<div className="relative w-20 h-20 rounded-sm overflow-hidden bg-white/[0.05] border border-white/[0.1] mb-2 group cursor-pointer">
|
||||
<img
|
||||
src={avatar.url}
|
||||
alt={avatar.name}
|
||||
className="w-full h-full object-cover bg-white/[0.05]"
|
||||
onError={(e) => {
|
||||
// 如果裁剪的头像加载失败,回退到原图
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.src = activeImageUrl;
|
||||
}}
|
||||
/>
|
||||
<div className="relative w-20 h-20 rounded-sm overflow-hidden bg-white/[0.05] border border-white/[0.1] mb-2 group cursor-pointer">
|
||||
<img
|
||||
src={avatar.url}
|
||||
alt={avatar.name}
|
||||
className="w-full h-full object-cover bg-white/[0.05]"
|
||||
onError={(e) => {
|
||||
// 如果裁剪的头像加载失败,回退到原图
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.src = activeImageUrl;
|
||||
}}
|
||||
/>
|
||||
{/* 删除角色按钮 - 使用Tooltip并调整z-index避免被遮挡 */}
|
||||
<Tooltip
|
||||
title="Remove this character from the movie"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user