"use client";
import { useState, useEffect, useRef } from "react";
import {
ChevronDown,
ChevronUp,
Video,
Loader2,
Lightbulb,
Package,
Crown,
Clapperboard,
Globe,
Clock,
Trash2,
LayoutTemplate,
ImagePlay,
Sparkles,
Settings,
} from "lucide-react";
import {
Dropdown,
Modal,
Tooltip,
Upload,
Popconfirm,
Image,
Popover,
} from "antd";
import { UploadOutlined } from "@ant-design/icons";
import { StoryTemplateEntity } from "@/app/service/domain/Entities";
import { useImageStoryServiceHook } from "@/app/service/Interaction/ImageStoryService";
import TemplateCard from "./templateCard";
import { AudioRecorder } from "./AudioRecorder";
import { useTemplateStoryServiceHook } from "@/app/service/Interaction/templateStoryService";
import { useRouter } from "next/navigation";
import { createMovieProjectV1 } from "@/api/video_flow";
import { MovieProjectService, MovieProjectMode } from "@/app/service/Interaction/MovieProjectService";
import { useLoadScriptText, useUploadFile } from "@/app/service/domain/service";
import { ActionButton } from "../common/ActionButton";
import { HighlightEditor } from "../common/HighlightEditor";
import GlobalLoad from "../common/GlobalLoad";
/**模板故事模式弹窗组件 */
const RenderTemplateStoryMode = ({
isOpen,
onClose,
configOptions = {
mode: "auto" as "auto" | "manual",
resolution: "720p" as "720p" | "1080p" | "4k",
language: "english",
videoDuration: "1min",
},
}: {
isOpen: boolean;
onClose: () => void;
configOptions: {
mode: "auto" | "manual";
resolution: "720p" | "1080p" | "4k";
language: string;
videoDuration: string;
};
}) => {
// 使用 hook 管理状态
const {
templateStoryList,
selectedTemplate,
activeRoleIndex,
activeRole,
isLoading,
getTemplateStoryList,
actionStory,
setSelectedTemplate,
setActiveRoleIndex,
AvatarAndAnalyzeFeatures,
setActiveRoleAudio,
updateFillableContentField,
handleFieldBlur,
clearData,
} = useTemplateStoryServiceHook();
// 使用上传文件hook
const { uploadFile, isUploading } = useUploadFile();
// 本地加载状态,用于 UI 反馈
const [localLoading, setLocalLoading] = useState(0);
// 控制输入框显示状态
const [inputVisible, setInputVisible] = useState<{ [key: string]: boolean }>(
{}
);
const router = useRouter();
// 组件挂载时获取模板列表
useEffect(() => {
if (isOpen) {
getTemplateStoryList();
}
}, [isOpen, getTemplateStoryList]);
// 监听点击外部区域关闭输入框
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as Element;
// 检查是否点击了输入框相关的元素
if (
!target.closest(".ant-tooltip") &&
!target.closest('[data-alt*="field-ai-button"]')
) {
// 关闭所有打开的输入框
setInputVisible({});
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
// 处理模板选择
const handleTemplateSelect = (template: StoryTemplateEntity) => {
setSelectedTemplate(template);
setActiveRoleIndex(0); // 重置角色选择
};
// 处理确认操作
const handleConfirm = async () => {
if (!selectedTemplate) return;
let timer = setInterval(() => {
setLocalLoading((prev) => {
if (prev >= 95) {
clearInterval(timer);
return 95;
}
return prev + 0.1;
});
}, 100);
try {
setLocalLoading(1);
// 获取当前用户信息
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
if (!User.id) {
console.error("用户未登录");
return;
}
const projectId = await actionStory(
String(User.id),
configOptions.mode,
configOptions.resolution,
configOptions.language
);
if (projectId) {
// 跳转到电影详情页
router.push(`/create/work-flow?episodeId=${projectId}`);
onClose();
// 重置状态
setSelectedTemplate(null);
setActiveRoleIndex(0);
}
console.log("Story action created:", projectId);
} catch (error) {
console.error("Failed to create story action:", error);
// 这里可以添加 toast 提示
onClose();
// 重置状态
setSelectedTemplate(null);
setActiveRoleIndex(0);
} finally {
setLocalLoading(0);
clearInterval(timer);
}
};
// 模板列表渲染
const templateListRender = () => {
return (
{templateStoryList.map((template, index) => (
handleTemplateSelect(template)}
>
))}
);
};
// 故事编辑器渲染
const storyEditorRender = () => {
return selectedTemplate ? (
{/* 模板信息头部 - 增加顶部空间 */}
{/* 左侧图片 */}
{/* 右侧信息 - 增加文本渲染空间 */}
{selectedTemplate.name}
{selectedTemplate.generateText}
{/* 变量字段填写区域 */}
{selectedTemplate?.fillable_content &&
selectedTemplate.fillable_content.length > 0 && (
Template Configuration
{selectedTemplate.fillable_content.map((field, index) => (
{/* 图片容器 */}
}
placement="top"
classNames={{
root: "max-w-none",
}}
open={inputVisible[field.field_name]}
onOpenChange={(visible) =>
setInputVisible((prev) => ({
...prev,
[field.field_name]: visible,
}))
}
trigger="contextMenu"
styles={{ root: { zIndex: 1000 } }}
>
{/* 图片 */}
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"
/>
{/* 角色名称 - 图片下方 */}
{field.field_name}
{/* 按钮组 - 右上角 */}
{/* AI生成按钮 */}
{/* 上传按钮 */}
{
const isImage = file.type.startsWith("image/");
if (!isImage) {
console.error("只能上传图片文件");
return false;
}
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;
const uploadedUrl = await uploadFile(
fileObj,
(progress) => {
console.log(`上传进度: ${progress}%`);
}
);
await AvatarAndAnalyzeFeatures(
uploadedUrl,
field.field_name
);
onSuccess?.(uploadedUrl);
} catch (error) {
console.error("字段图片上传失败:", error);
onError?.(error as Error);
}
}}
>
))}
)}
{/* 角色自定义部分 - 精简布局 */}
{/*
Character Customization
{/* 紧凑布局 */}
{/*
{/* 左侧:音频部分 */}
{/*
{/* 音频操作区域 - 使用新的 AudioRecorder 组件 */}
{/*
{
setActiveRoleAudio(audioUrl);
}}
onAudioDeleted={() => {
setActiveRoleAudio("");
}}
/>
{/* 右侧:角色图片缩略图列表 - 精简 */}
{/*
{selectedTemplate.storyRole.map((role, index: number) => (
{/* 上传按钮 - 右上角 */}
{/*
{
// 验证文件类型
const isImage = file.type.startsWith("image/");
if (!isImage) {
console.error("只能上传图片文件");
return false;
}
// 验证文件大小 (10MB)
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
console.error("图片大小不能超过10MB");
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);
// 上传成功后,更新角色图片
await AvatarAndAnalyzeFeatures(uploadedUrl);
onSuccess?.(uploadedUrl);
} catch (error) {
console.error("图片上传失败:", error);
onError?.(error as Error);
}
}}
>
))}
*/}
0}
handleCreateVideo={handleConfirm}
icon={}
/>
) : (
No templates available
Please try again later
);
};
return (
<>
{
// 清空所有选中的内容数据
clearData();
onClose();
}}
footer={null}
width="60%"
closable={false}
style={{ maxWidth: "800px", marginTop: "0vh" }}
className="photo-story-modal !pb-0 rounded-lg bg-white/[0.08] backdrop-blur-[20px] [&_.ant-modal-content]:bg-white/[0.00]"
>
{/* 弹窗头部 */}
Template Story Selection
{templateListRender()}
{storyEditorRender()}
>
);
};
/**
* 视频工具面板组件
* 提供脚本输入和视频克隆两种模式,支持展开/收起功能
*/
export function ChatInputBox({ noData }: { noData: boolean }) {
// 控制面板展开/收起状态
const [isExpanded, setIsExpanded] = useState(false);
// 模板故事弹窗状态
const [isTemplateModalOpen, setIsTemplateModalOpen] = useState(false);
// 图片故事弹窗状态
const [isPhotoStoryModalOpen, setIsPhotoStoryModalOpen] = useState(false);
// 共享状态 - 需要在不同渲染函数间共享
const [script, setScript] = useState(""); // 用户输入的脚本内容
const router = useRouter();
const [loadingIdea, setLoadingIdea] = useState(false); // 获取创意建议时的加载状态
const [isCreating, setIsCreating] = useState(false); // 视频创建过程中的加载状态
// 配置选项状态 - 整合所有配置项到一个对象
const [configOptions, setConfigOptions] = useState<{
mode: "auto" | "manual";
resolution: "720p" | "1080p" | "4k";
language: string;
videoDuration: string;
}>({
mode: "auto",
resolution: "720p",
language: "english",
videoDuration: "1min",
});
// 配置项显示控制状态
const [showConfigOptions, setShowConfigOptions] = useState(false);
const handleGetIdea = () => {
if (loadingIdea) return;
setLoadingIdea(true);
const ideaText =
"a cute capybara with an orange on its head, staring into the distance and walking forward";
setTimeout(() => {
setScript(ideaText);
setLoadingIdea(false);
}, 3000);
};
// Handle creating video
const handleCreateVideo = async () => {
setIsCreating(true);
if (!script) {
setIsCreating(false);
return;
}
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
// 创建剧集数据
let episodeData: any = {
user_id: String(User.id),
script: script,
mode: configOptions.mode,
resolution: configOptions.resolution,
language: configOptions.language,
video_duration: configOptions.videoDuration,
};
// 调用创建剧集API
try {
const result = await MovieProjectService.createProject(
MovieProjectMode.NORMAL,
episodeData
);
const episodeId = result.project_id;
router.push(`/create/work-flow?episodeId=${episodeId}`);
} catch (error) {
console.error("创建剧集失败:", error);
} finally {
setIsCreating(false);
}
};
return (
{/* 视频故事板工具面板 - 毛玻璃效果背景 */}
{/* 展开/收起控制区域 */}
{
!noData && (
<>
{isExpanded ? (
// 展开状态:显示收起按钮和提示
setIsExpanded(false)}
>
Click to action
) : (
// 收起状态:显示展开按钮
setIsExpanded(true)}
>
)}
>
)
}
{/* 主要内容区域 - 简化层级,垂直居中 */}
{/* 右上角齿轮图标和配置项 */}
{!isExpanded && (
{/* 使用 Dropdown 替代手动控制显示/隐藏 */}
(
setConfigOptions((prev) => ({ ...prev, [key]: value }))
}
/>
)}
placement={"left" as any}
trigger={["click"]}
>
{/* 配置项显示控制按钮 - 齿轮图标 */}
)}
{/* 输入框和Action按钮 - 只在展开状态显示 */}
{!isExpanded && (
{/* 第一行:输入框 */}
{/* 文本输入框 - 改为textarea */}
{/* 第二行:功能按钮和Action按钮 - 同一行 */}
{/* 左侧功能按钮区域 */}
{/* 获取创意按钮 */}
{/* 分隔线 */}
{/* 模板故事按钮 */}
{/* 分隔线 */}
{/* 图片故事按钮 */}
{/* 图片故事弹窗 */}
setIsPhotoStoryModalOpen(false)}
configOptions={configOptions}
/>
{/* 右侧Action按钮 */}
}
/>
)}
{/* 配置选项区域 - 已移至右上角 */}
{/*
setConfigOptions((prev) => ({ ...prev, [key]: value }))
}
/> */}
{/* 模板故事弹窗 */}
setIsTemplateModalOpen(false)}
/>
);
}
/**
* 配置选项组件
* 提供视频创建的各种配置选项,位于输入框下方
*/
const ConfigOptions = ({
config,
onConfigChange,
}: {
config: {
mode: string;
resolution: string;
language: string;
videoDuration: string;
};
onConfigChange: (key: string, value: string) => void;
}) => {
const configItems = [
{
key: "mode",
icon: Package,
options: [
{ value: "auto", label: "Auto", isVip: false },
{ value: "manual", label: "Manual", isVip: true },
],
},
{
key: "resolution",
icon: Video,
options: [
{ value: "720p", label: "720P", isVip: false },
{ value: "1080p", label: "1080P", isVip: true },
{ value: "2k", label: "2K", isVip: true },
{ value: "4k", label: "4K", isVip: true },
],
},
{
key: "language",
icon: Globe,
options: [
{ value: "english", label: "English", isVip: false },
{ value: "chinese", label: "Chinese", isVip: true },
{ value: "japanese", label: "Japanese", isVip: true },
{ value: "korean", label: "Korean", isVip: true },
{ value: "spanish", label: "Spanish", isVip: true },
{ value: "portuguese", label: "Portuguese", isVip: true },
{ value: "hindi", label: "Hindi", isVip: true },
{ value: "japanese", label: "Japanese", isVip: true },
{ value: "korean", label: "Korean", isVip: true },
{ value: "arabic", label: "Arabic", isVip: true },
{ value: "russian", label: "Russian", isVip: true },
],
},
{
key: "videoDuration",
icon: Clock,
options: [
{ value: "1min", label: "1 Min", isVip: false },
{ value: "2min", label: "2 Min", isVip: true },
{ value: "3min", label: "3 Min", isVip: true },
],
},
];
return (
{configItems.map((item) => {
const IconComponent = item.icon;
const currentOption = item.options.find(
(opt) => opt.value === config[item.key as keyof typeof config]
);
return (
({
key: option.value,
label: (
{option.label}
{option.isVip && (
)}
),
})),
onClick: ({ key }) => onConfigChange(item.key, key),
}}
trigger={["click"]}
placement="topRight"
>
);
})}
);
};
/**
* 图片故事弹窗组件
* 提供图片上传、AI分析和故事生成功能,支持动态UI变化
*/
const PhotoStoryModal = ({
isOpen,
onClose,
configOptions = {
mode: "auto" as "auto" | "manual",
resolution: "720p" as "720p" | "1080p" | "4k",
language: "english",
videoDuration: "1min",
},
}: {
isOpen: boolean;
onClose: () => void;
configOptions?: {
mode: "auto" | "manual";
resolution: "720p" | "1080p" | "4k";
language: string;
videoDuration: string;
};
}) => {
// 使用图片故事服务hook管理状态
const {
activeImageUrl,
storyContent,
potentialGenres,
selectedCategory,
isLoading,
hasAnalyzed,
taskProgress,
updateStoryType,
updateStoryContent,
updateCharacterName,
resetImageStory,
triggerFileSelection,
avatarComputed,
uploadAndAnalyzeImage,
setCharactersAnalysis,
originalUserDescription,
actionMovie,
uploadCharacterAvatarAndAnalyzeFeatures,
} = useImageStoryServiceHook();
const { loadingText } = useLoadScriptText(isLoading);
const [localLoading, setLocalLoading] = useState(0);
// 重置状态
const handleClose = () => {
// resetImageStory();
onClose();
};
const router = useRouter();
const taskProgressRef = useRef(taskProgress);
const [cursorPosition, setCursorPosition] = useState(0);
const handleCursorPositionChange = (position: number) => {
setCursorPosition(position);
};
useEffect(() => {
taskProgressRef.current = taskProgress;
}, [taskProgress]);
// 处理图片上传
const handleImageUpload = async (e: any) => {
const target = e.target as HTMLImageElement;
if (
!(target.tagName == "IMG" || e.target.dataset.alt == "image-upload-area")
) {
return;
}
e.preventDefault();
e.stopPropagation();
try {
await triggerFileSelection();
} catch (error) {
console.error("Failed to upload image:", error);
}
};
// 处理确认
const handleConfirm = async () => {
try {
// 获取当前用户信息
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
if (!User.id) {
console.error("用户未登录");
return;
}
// 调用actionMovie接口
const episodeResponse = await actionMovie(
String(User.id),
configOptions.mode as "auto" | "manual",
configOptions.resolution as "720p" | "1080p" | "4k",
configOptions.language
);
if (!episodeResponse) return;
let episodeId = episodeResponse.project_id;
// let episodeId = '9c34fcc4-c8d8-44fc-879e-9bd56f608c76';
router.push(`/create/work-flow?episodeId=${episodeId}`);
// 成功后关闭弹窗
handleClose();
} catch (error) {
console.error("创建电影项目失败:", error);
}
};
const handleAnalyzeImage = async () => {
let timeout = 100;
let timer: NodeJS.Timeout;
timer = setInterval(() => {
const currentProgress = taskProgressRef.current;
setLocalLoading((prev) => {
if (prev >= currentProgress && currentProgress != 0) {
return currentProgress;
}
return prev + 0.1;
});
}, timeout);
try {
await uploadAndAnalyzeImage();
} finally {
clearInterval(timer);
setLocalLoading(0);
}
};
return (
×
}
>
0} progress={localLoading}>
{/* 弹窗头部 */}
Movie Generation from Image
{/* 左侧:图片上传 */}
{activeImageUrl ? (
{
resetImageStory();
}}
okText="Yes"
cancelText="No"
showCancel={false}
okType="default"
placement="top"
classNames={{
root: "text-white event-pointer",
body: "text-white border rounded-lg bg-white/[0.04] [&_.ant-popconfirm-description]:!text-white [&_.ant-popconfirm-title]:!text-white [&_.ant-btn]:!text-white",
}}
>
) : (
)}
{/* 中间:头像展示(分析后显示) */}
{hasAnalyzed && avatarComputed.length > 0 && (
{avatarComputed.map((avatar, index) => (

{
// 如果裁剪的头像加载失败,回退到原图
const target = e.target as HTMLImageElement;
target.src = activeImageUrl;
}}
/>
{/* 删除角色按钮 - 使用Tooltip并调整z-index避免被遮挡 */}
{/* 上传新图片按钮 - 悬停时显示 */}
))}
)}
{/* 右侧:分类选择(分析后显示) */}
{hasAnalyzed && potentialGenres.length > 0 && (
{[...potentialGenres].map((genre) => (
))}
)}
{/* 原始用户描述的展示 */}
{originalUserDescription && (
Your Provided Text:{originalUserDescription}
)}
{/* 文本输入框 */}
{!hasAnalyzed ? (
// 分析按钮 - 使用ActionButton样式
}
/>
) : (
<>
{/* Action按钮 - 使用ActionButton样式 */}
}
/>
>
)}
);
};