forked from 77media/video-flow
推进
This commit is contained in:
parent
87efe72eeb
commit
f55724a545
@ -221,21 +221,21 @@ export const useImageStoryServiceHook = (): UseImageStoryService => {
|
|||||||
return charactersAnalysis.map((character) => {
|
return charactersAnalysis.map((character) => {
|
||||||
console.log('character', character)
|
console.log('character', character)
|
||||||
// 如果已经有头像URL,直接返回
|
// 如果已经有头像URL,直接返回
|
||||||
if (character.avatarUrl) {
|
if (character.crop_url) {
|
||||||
return {
|
return {
|
||||||
name: character.role_name,
|
name: character.role_name,
|
||||||
url: character.avatarUrl,
|
url: character.crop_url,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步生成头像URL
|
// // 异步生成头像URL
|
||||||
generateAvatarFromRegion(character, activeImageUrl);
|
// generateAvatarFromRegion(character, activeImageUrl);
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
name: character.role_name,
|
// name: character.role_name,
|
||||||
url: "", // 初始为空,异步生成完成后会更新
|
// url: "", // 初始为空,异步生成完成后会更新
|
||||||
};
|
// };
|
||||||
});
|
}).filter(Boolean) as { name: string; url: string }[];
|
||||||
}, [charactersAnalysis, activeImageUrl, generateAvatarFromRegion]);
|
}, [charactersAnalysis, activeImageUrl, generateAvatarFromRegion]);
|
||||||
/**
|
/**
|
||||||
* 上传图片并分析
|
* 上传图片并分析
|
||||||
|
|||||||
@ -35,7 +35,7 @@ import StarterKit from "@tiptap/starter-kit";
|
|||||||
import { HighlightTextExtension } from "@/components/ui/main-editor/HighlightText";
|
import { HighlightTextExtension } from "@/components/ui/main-editor/HighlightText";
|
||||||
import Placeholder from "@tiptap/extension-placeholder";
|
import Placeholder from "@tiptap/extension-placeholder";
|
||||||
import { createMovieProjectV1 } from "@/api/video_flow";
|
import { createMovieProjectV1 } from "@/api/video_flow";
|
||||||
import { useLoadScriptText } from "@/app/service/domain/service";
|
import { useLoadScriptText, useUploadFile } from "@/app/service/domain/service";
|
||||||
|
|
||||||
// 自定义音频播放器样式
|
// 自定义音频播放器样式
|
||||||
const customAudioPlayerStyles = `
|
const customAudioPlayerStyles = `
|
||||||
@ -556,7 +556,7 @@ export function ChatInputBox() {
|
|||||||
|
|
||||||
// 调用创建剧集API
|
// 调用创建剧集API
|
||||||
const episodeResponse = await createMovieProjectV1(episodeData);
|
const episodeResponse = await createMovieProjectV1(episodeData);
|
||||||
console.log('episodeResponse', episodeResponse);
|
console.log("episodeResponse", episodeResponse);
|
||||||
if (episodeResponse.code !== 0) {
|
if (episodeResponse.code !== 0) {
|
||||||
console.error(`创建剧集失败: ${episodeResponse.message}`);
|
console.error(`创建剧集失败: ${episodeResponse.message}`);
|
||||||
alert(`创建剧集失败: ${episodeResponse.message}`);
|
alert(`创建剧集失败: ${episodeResponse.message}`);
|
||||||
@ -704,11 +704,11 @@ export function ChatInputBox() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* 图片故事弹窗 */}
|
{/* 图片故事弹窗 */}
|
||||||
<PhotoStoryModal
|
<PhotoStoryModal
|
||||||
isOpen={isPhotoStoryModalOpen}
|
isOpen={isPhotoStoryModalOpen}
|
||||||
onClose={() => setIsPhotoStoryModalOpen(false)}
|
onClose={() => setIsPhotoStoryModalOpen(false)}
|
||||||
configOptions={configOptions}
|
configOptions={configOptions}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 右侧Action按钮 */}
|
{/* 右侧Action按钮 */}
|
||||||
@ -762,7 +762,6 @@ const ActionButton = ({
|
|||||||
<button
|
<button
|
||||||
name="text"
|
name="text"
|
||||||
className="w-full h-full opacity-90 rounded-xl bg-black flex items-center justify-center"
|
className="w-full h-full opacity-90 rounded-xl bg-black flex items-center justify-center"
|
||||||
|
|
||||||
>
|
>
|
||||||
{isCreating ? <Loader2 className="w-5 h-5 animate-spin" /> : icon}
|
{isCreating ? <Loader2 className="w-5 h-5 animate-spin" /> : icon}
|
||||||
</button>
|
</button>
|
||||||
@ -1042,9 +1041,10 @@ const PhotoStoryModal = ({
|
|||||||
uploadAndAnalyzeImage,
|
uploadAndAnalyzeImage,
|
||||||
setCharactersAnalysis,
|
setCharactersAnalysis,
|
||||||
originalUserDescription,
|
originalUserDescription,
|
||||||
actionMovie
|
actionMovie,
|
||||||
} = useImageStoryServiceHook();
|
} = useImageStoryServiceHook();
|
||||||
const { loadingText } = useLoadScriptText(isLoading);
|
const { loadingText } = useLoadScriptText(isLoading);
|
||||||
|
const { uploadFile } = useUploadFile();
|
||||||
// 重置状态
|
// 重置状态
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
resetImageStory();
|
resetImageStory();
|
||||||
@ -1072,13 +1072,13 @@ const PhotoStoryModal = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 调用actionMovie接口
|
// 调用actionMovie接口
|
||||||
const episodeResponse = await actionMovie(
|
const episodeResponse = await actionMovie(
|
||||||
String(User.id),
|
String(User.id),
|
||||||
configOptions.mode as "auto" | "manual",
|
configOptions.mode as "auto" | "manual",
|
||||||
configOptions.resolution as "720p" | "1080p" | "4k",
|
configOptions.resolution as "720p" | "1080p" | "4k",
|
||||||
configOptions.language
|
configOptions.language
|
||||||
);
|
);
|
||||||
if(!episodeResponse) return
|
if (!episodeResponse) return;
|
||||||
let episodeId = episodeResponse.project_id;
|
let episodeId = episodeResponse.project_id;
|
||||||
// let episodeId = '9c34fcc4-c8d8-44fc-879e-9bd56f608c76';
|
// let episodeId = '9c34fcc4-c8d8-44fc-879e-9bd56f608c76';
|
||||||
router.push(`/create/work-flow?episodeId=${episodeId}`);
|
router.push(`/create/work-flow?episodeId=${episodeId}`);
|
||||||
@ -1096,7 +1096,7 @@ const PhotoStoryModal = ({
|
|||||||
footer={null}
|
footer={null}
|
||||||
width="80%"
|
width="80%"
|
||||||
style={{ maxWidth: "1000px", marginTop: "10vh" }}
|
style={{ maxWidth: "1000px", marginTop: "10vh" }}
|
||||||
className="photo-story-modal"
|
className="photo-story-modal bg-white/[0.08] backdrop-blur-[20px] [&_.ant-modal-content]:bg-white/[0.00]"
|
||||||
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">
|
||||||
<span className="text-white/70 text-lg leading-none flex items-center justify-center">
|
<span className="text-white/70 text-lg leading-none flex items-center justify-center">
|
||||||
@ -1114,7 +1114,7 @@ const PhotoStoryModal = ({
|
|||||||
Movie Generation from Image
|
Movie Generation from Image
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-white/[0.05] border border-white/[0.1] rounded-xl p-4 mt-2">
|
<div className="w-full bg-white/[0.04] border border-white/[0.1] rounded-xl p-4 mt-2">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
{/* 左侧:图片上传 */}
|
{/* 左侧:图片上传 */}
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
@ -1164,7 +1164,7 @@ const PhotoStoryModal = ({
|
|||||||
key={`${avatar.name}-${index}`}
|
key={`${avatar.name}-${index}`}
|
||||||
className="flex flex-col items-center"
|
className="flex flex-col items-center"
|
||||||
>
|
>
|
||||||
<div className="relative w-14 h-14 rounded-sm overflow-hidden bg-white/[0.05] border border-white/[0.1] mb-2 group">
|
<div className="relative w-14 h-14 rounded-sm overflow-hidden bg-white/[0.05] border border-white/[0.1] mb-2 group cursor-pointer">
|
||||||
<img
|
<img
|
||||||
src={avatar.url}
|
src={avatar.url}
|
||||||
alt={avatar.name}
|
alt={avatar.name}
|
||||||
@ -1194,7 +1194,10 @@ const PhotoStoryModal = ({
|
|||||||
// 从故事内容中删除该角色的所有标签和引用
|
// 从故事内容中删除该角色的所有标签和引用
|
||||||
const updatedStory = storyContent
|
const updatedStory = storyContent
|
||||||
.replace(
|
.replace(
|
||||||
new RegExp(`<role[^>]*>${avatar.name}<\/role>`, "g"),
|
new RegExp(
|
||||||
|
`<role[^>]*>${avatar.name}<\/role>`,
|
||||||
|
"g"
|
||||||
|
),
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
@ -1211,6 +1214,61 @@ const PhotoStoryModal = ({
|
|||||||
<Trash2 className="w-2.5 h-2.5" />
|
<Trash2 className="w-2.5 h-2.5" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{/* 上传新图片按钮 - 悬停时显示 */}
|
||||||
|
<Tooltip
|
||||||
|
title="Click to upload new image for this character"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
// 创建隐藏的文件输入框
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "file";
|
||||||
|
input.accept = "image/*";
|
||||||
|
input.style.display = "none";
|
||||||
|
|
||||||
|
input.onchange = async (event) => {
|
||||||
|
const target =
|
||||||
|
event.target as HTMLInputElement;
|
||||||
|
const file = target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
try {
|
||||||
|
// 使用七牛云上传
|
||||||
|
const newImageUrl = await uploadFile(
|
||||||
|
file
|
||||||
|
);
|
||||||
|
|
||||||
|
// 更新角色分析中的图片URL
|
||||||
|
setCharactersAnalysis((prev) =>
|
||||||
|
prev.map((char) =>
|
||||||
|
char.role_name === avatar.name
|
||||||
|
? { ...char, crop_url: newImageUrl }
|
||||||
|
: char
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 清理临时元素
|
||||||
|
document.body.removeChild(input);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("上传图片失败:", error);
|
||||||
|
// 清理临时元素
|
||||||
|
if (document.body.contains(input)) {
|
||||||
|
document.body.removeChild(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加到DOM并触发点击
|
||||||
|
document.body.appendChild(input);
|
||||||
|
input.click();
|
||||||
|
}}
|
||||||
|
className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<Upload className="w-4 h-4 text-white" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<input
|
<input
|
||||||
@ -1236,7 +1294,7 @@ const PhotoStoryModal = ({
|
|||||||
{hasAnalyzed && potentialGenres.length > 0 && (
|
{hasAnalyzed && potentialGenres.length > 0 && (
|
||||||
<div className="flex-shrink-0 animate-in fade-in-0 slide-in-from-right-4 duration-300">
|
<div className="flex-shrink-0 animate-in fade-in-0 slide-in-from-right-4 duration-300">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{[ ...potentialGenres].map((genre) => (
|
{[...potentialGenres].map((genre) => (
|
||||||
<button
|
<button
|
||||||
key={genre}
|
key={genre}
|
||||||
onClick={() => updateStoryType(genre)}
|
onClick={() => updateStoryType(genre)}
|
||||||
@ -1255,7 +1313,9 @@ const PhotoStoryModal = ({
|
|||||||
</div>
|
</div>
|
||||||
{/* 原始用户描述的展示 */}
|
{/* 原始用户描述的展示 */}
|
||||||
{originalUserDescription && (
|
{originalUserDescription && (
|
||||||
<div className="mt-2 text-sm text-white/30 italic">Your Provided Text:{originalUserDescription}</div>
|
<div className="mt-2 text-sm text-white/30 italic">
|
||||||
|
Your Provided Text:{originalUserDescription}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-start gap-4 mt-2 relative">
|
<div className="flex items-start gap-4 mt-2 relative">
|
||||||
|
|||||||
@ -46,7 +46,7 @@ export function ThumbnailGrid({
|
|||||||
const container = thumbnailsRef.current;
|
const container = thumbnailsRef.current;
|
||||||
const thumbnailWidth = container.offsetWidth / 4; // 每个缩略图宽度(包含间距)
|
const thumbnailWidth = container.offsetWidth / 4; // 每个缩略图宽度(包含间距)
|
||||||
const scrollPosition = currentSketchIndex * thumbnailWidth;
|
const scrollPosition = currentSketchIndex * thumbnailWidth;
|
||||||
|
|
||||||
container.scrollTo({
|
container.scrollTo({
|
||||||
left: scrollPosition,
|
left: scrollPosition,
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
@ -68,13 +68,13 @@ export function ThumbnailGrid({
|
|||||||
|
|
||||||
// 使用 useRef 存储前一次的数据,避免触发重渲染
|
// 使用 useRef 存储前一次的数据,避免触发重渲染
|
||||||
const prevDataRef = useRef<any[]>([]);
|
const prevDataRef = useRef<any[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentData = getCurrentData();
|
const currentData = getCurrentData();
|
||||||
if (currentData && currentData.length > 0) {
|
if (currentData && currentData.length > 0) {
|
||||||
const currentDataStr = JSON.stringify(currentData);
|
const currentDataStr = JSON.stringify(currentData);
|
||||||
const prevDataStr = JSON.stringify(prevDataRef.current);
|
const prevDataStr = JSON.stringify(prevDataRef.current);
|
||||||
|
|
||||||
// 只有当数据真正发生变化时才进行处理
|
// 只有当数据真正发生变化时才进行处理
|
||||||
if (currentDataStr !== prevDataStr) {
|
if (currentDataStr !== prevDataStr) {
|
||||||
// 找到最新更新的数据项的索引
|
// 找到最新更新的数据项的索引
|
||||||
@ -84,14 +84,14 @@ export function ThumbnailGrid({
|
|||||||
// 检查数据是否发生变化(包括状态变化)
|
// 检查数据是否发生变化(包括状态变化)
|
||||||
return JSON.stringify(item) !== JSON.stringify(prevDataRef.current[index]);
|
return JSON.stringify(item) !== JSON.stringify(prevDataRef.current[index]);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('changedIndex_thumbnail-grid', changedIndex, 'currentData:', currentData, 'prevData:', prevDataRef.current);
|
console.log('changedIndex_thumbnail-grid', changedIndex, 'currentData:', currentData, 'prevData:', prevDataRef.current);
|
||||||
|
|
||||||
// 如果找到变化的项,自动选择该项
|
// 如果找到变化的项,自动选择该项
|
||||||
if (changedIndex !== -1) {
|
if (changedIndex !== -1) {
|
||||||
onSketchSelect(changedIndex);
|
onSketchSelect(changedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新前一次的数据快照
|
// 更新前一次的数据快照
|
||||||
prevDataRef.current = JSON.parse(JSON.stringify(currentData));
|
prevDataRef.current = JSON.parse(JSON.stringify(currentData));
|
||||||
}
|
}
|
||||||
@ -103,10 +103,10 @@ export function ThumbnailGrid({
|
|||||||
const currentData = getCurrentData();
|
const currentData = getCurrentData();
|
||||||
const maxIndex = currentData.length - 1;
|
const maxIndex = currentData.length - 1;
|
||||||
console.log('handleKeyDown', maxIndex, 'isFocused:', isFocused);
|
console.log('handleKeyDown', maxIndex, 'isFocused:', isFocused);
|
||||||
|
|
||||||
if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && maxIndex >= 0) {
|
if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && maxIndex >= 0) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
let newIndex = currentSketchIndex;
|
let newIndex = currentSketchIndex;
|
||||||
if (e.key === 'ArrowLeft') {
|
if (e.key === 'ArrowLeft') {
|
||||||
// 向左循环
|
// 向左循环
|
||||||
@ -115,7 +115,7 @@ export function ThumbnailGrid({
|
|||||||
// 向右循环
|
// 向右循环
|
||||||
newIndex = currentSketchIndex === maxIndex ? 0 : currentSketchIndex + 1;
|
newIndex = currentSketchIndex === maxIndex ? 0 : currentSketchIndex + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('切换索引:', currentSketchIndex, '->', newIndex, '最大索引:', maxIndex);
|
console.log('切换索引:', currentSketchIndex, '->', newIndex, '最大索引:', maxIndex);
|
||||||
onSketchSelect(newIndex);
|
onSketchSelect(newIndex);
|
||||||
}
|
}
|
||||||
@ -188,16 +188,16 @@ export function ThumbnailGrid({
|
|||||||
const currentSketch = taskSketch[currentSketchIndex];
|
const currentSketch = taskSketch[currentSketchIndex];
|
||||||
const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)'];
|
const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)'];
|
||||||
const bgColors = currentSketch?.bg_rgb || defaultBgColors;
|
const bgColors = currentSketch?.bg_rgb || defaultBgColors;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="relative aspect-video rounded-lg overflow-hidden"
|
className="relative aspect-video rounded-lg overflow-hidden"
|
||||||
initial={{ opacity: 0, scale: 0.8 }}
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
>
|
>
|
||||||
{/* 动态渐变背景 */}
|
{/* 动态渐变背景 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className={`absolute inset-0 bg-gradient-to-r from-[${bgColors[0]}] via-[${bgColors[1]}] to-[${bgColors[2]}]`}
|
className={`absolute inset-0 bg-gradient-to-r from-[${bgColors[0]}] via-[${bgColors[1]}] to-[${bgColors[2]}]`}
|
||||||
animate={{
|
animate={{
|
||||||
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
|
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
|
||||||
@ -212,7 +212,7 @@ export function ThumbnailGrid({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* 动态光效 */}
|
{/* 动态光效 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute inset-0 opacity-50"
|
className="absolute inset-0 opacity-50"
|
||||||
style={{
|
style={{
|
||||||
background: "radial-gradient(circle at center, rgba(255,255,255,0.8) 0%, transparent 50%)",
|
background: "radial-gradient(circle at center, rgba(255,255,255,0.8) 0%, transparent 50%)",
|
||||||
@ -230,11 +230,11 @@ export function ThumbnailGrid({
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute -inset-4 bg-gradient-to-r from-white via-sky-200 to-cyan-200 rounded-full opacity-60 blur-xl"
|
className="absolute -inset-4 bg-gradient-to-r from-white via-sky-200 to-cyan-200 rounded-full opacity-60 blur-xl"
|
||||||
animate={{
|
animate={{
|
||||||
scale: [1, 1.2, 1],
|
scale: [1, 1.2, 1],
|
||||||
rotate: [0, 180, 360],
|
rotate: [0, 180, 360],
|
||||||
}}
|
}}
|
||||||
transition={{
|
transition={{
|
||||||
duration: 4,
|
duration: 4,
|
||||||
repeat: Infinity,
|
repeat: Infinity,
|
||||||
ease: "linear"
|
ease: "linear"
|
||||||
@ -252,7 +252,7 @@ export function ThumbnailGrid({
|
|||||||
// 渲染视频阶段的缩略图
|
// 渲染视频阶段的缩略图
|
||||||
const renderVideoThumbnails = () => (
|
const renderVideoThumbnails = () => (
|
||||||
taskObject.videos.data.map((video, index) => {
|
taskObject.videos.data.map((video, index) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`video-${index}`}
|
key={`video-${index}`}
|
||||||
@ -279,7 +279,7 @@ export function ThumbnailGrid({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{taskObject.videos.data[index].urls ? (
|
{taskObject.videos.data[index].urls ? (
|
||||||
<video
|
<video
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
src={taskObject.videos.data[index].urls[0]}
|
src={taskObject.videos.data[index].urls[0]}
|
||||||
playsInline
|
playsInline
|
||||||
@ -288,17 +288,17 @@ export function ThumbnailGrid({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
|
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
|
||||||
<img
|
<img
|
||||||
className={`w-full h-full object-cover transition-all duration-300 select-none ${
|
className={`w-full h-full object-cover transition-all duration-300 select-none ${
|
||||||
(!taskObject.shot_sketch.data[index]) ? 'filter blur-sm opacity-60' : ''
|
(!taskObject.shot_sketch.data[index]) ? 'filter blur-sm opacity-60' : ''
|
||||||
}`}
|
}`}
|
||||||
src={taskObject.shot_sketch.data[index] ? taskObject.shot_sketch.data[index].url : video.urls[0]}
|
src={taskObject.shot_sketch.data[index] ? taskObject.shot_sketch.data[index].url : video.urls?.[0] || ''}
|
||||||
alt={`Thumbnail ${index + 1}`}
|
alt={`Thumbnail ${index + 1}`}
|
||||||
draggable="false"
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute bottom-0 left-0 right-0 p-2 bg-gradient-to-t from-black/60 to-transparent z-10">
|
<div className="absolute bottom-0 left-0 right-0 p-2 bg-gradient-to-t from-black/60 to-transparent z-10">
|
||||||
@ -313,7 +313,7 @@ export function ThumbnailGrid({
|
|||||||
const renderSketchThumbnails = (sketchData: any[]) => (
|
const renderSketchThumbnails = (sketchData: any[]) => (
|
||||||
<>
|
<>
|
||||||
{sketchData.map((sketch, index) => {
|
{sketchData.map((sketch, index) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`sketch-${index}`}
|
key={`sketch-${index}`}
|
||||||
@ -340,7 +340,7 @@ export function ThumbnailGrid({
|
|||||||
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
|
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
|
||||||
{(sketch.status === 1) && (
|
{(sketch.status === 1) && (
|
||||||
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
|
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
|
||||||
<img
|
<img
|
||||||
className="w-full h-full object-cover select-none"
|
className="w-full h-full object-cover select-none"
|
||||||
src={sketch.url}
|
src={sketch.url}
|
||||||
alt={`NG ${index + 1}`}
|
alt={`NG ${index + 1}`}
|
||||||
@ -359,7 +359,7 @@ export function ThumbnailGrid({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={thumbnailsRef}
|
ref={thumbnailsRef}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className="w-full h-full grid grid-flow-col auto-cols-[20%] gap-4 overflow-x-auto hide-scrollbar px-1 py-1 cursor-grab active:cursor-grabbing focus:outline-none select-none"
|
className="w-full h-full grid grid-flow-col auto-cols-[20%] gap-4 overflow-x-auto hide-scrollbar px-1 py-1 cursor-grab active:cursor-grabbing focus:outline-none select-none"
|
||||||
@ -377,4 +377,4 @@ export function ThumbnailGrid({
|
|||||||
{taskObject.currentStage === 'character' && renderSketchThumbnails(taskObject.scenes.data)}
|
{taskObject.currentStage === 'character' && renderSketchThumbnails(taskObject.scenes.data)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user