"use client";
import {
useState,
useRef,
useEffect,
forwardRef,
useImperativeHandle,
} from "react";
import {
ChevronDown,
ChevronUp,
Video,
Loader2,
Lightbulb,
Package,
Crown,
Clapperboard,
Globe,
AudioLines,
Clock,
Trash2,
Plus,
LayoutTemplate,
ImagePlay,
} from "lucide-react";
import { Dropdown, Modal, Tooltip, Upload, Image } from "antd";
import { PlusOutlined, UploadOutlined, EyeOutlined } 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";
// 自定义音频播放器样式
const customAudioPlayerStyles = `
.custom-audio-player {
background: rgba(255, 255, 255, 0.05) !important;
border-radius: 8px !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
}
.custom-audio-player .rhap_main-controls-button {
color: white !important;
}
.custom-audio-player .rhap_progress-filled {
background-color: #3b82f6 !important;
}
.custom-audio-player .rhap_progress-indicator {
background-color: #3b82f6 !important;
}
.custom-audio-player .rhap_time {
color: rgba(255, 255, 255, 0.7) !important;
}
.custom-audio-player .rhap_volume-controls {
color: white !important;
}
/* 模式选择下拉菜单样式 */
.mode-dropdown .ant-dropdown-menu {
background: rgba(255, 255, 255, 0.08) !important;
backdrop-filter: blur(20px) !important;
border: 1px solid rgba(255, 255, 255, 0.12) !important;
border-radius: 10px !important;
padding: 6px !important;
min-width: 160px !important;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3) !important;
}
.mode-dropdown .ant-dropdown-menu-item {
padding: 6px 10px !important;
border-radius: 6px !important;
color: rgba(255, 255, 255, 0.9) !important;
transition: all 0.2s ease !important;
margin-bottom: 3px !important;
}
.mode-dropdown .ant-dropdown-menu-item:hover {
background: rgba(255, 255, 255, 0.15) !important;
transform: translateX(4px) !important;
}
.mode-dropdown .ant-dropdown-menu-item:last-child {
margin-bottom: 0 !important;
}
/* 模式提示tooltip样式 */
.mode-tooltip .ant-tooltip-inner {
background: rgba(0, 0, 0, 0.8) !important;
backdrop-filter: blur(10px) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
border-radius: 8px !important;
color: white !important;
font-size: 12px !important;
line-height: 1.4 !important;
max-width: 200px !important;
}
.mode-tooltip .ant-tooltip-arrow::before {
background: rgba(0, 0, 0, 0.8) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
}
/* 模板卡片样式 */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* 自定义滚动条 */
.template-list-scroll::-webkit-scrollbar {
width: 4px;
}
.template-list-scroll::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 2px;
}
.template-list-scroll::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
}
.template-list-scroll::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
/* 自定义缩放类 */
.scale-102 {
transform: scale(1.02);
}
`;
/**模板故事模式弹窗组件 */
const RenderTemplateStoryMode = ({
isOpen,
onClose,
}: {
isOpen: boolean;
onClose: () => void;
}) => {
// 使用 hook 管理状态
const {
templateStoryList,
selectedTemplate,
activeRoleIndex,
activeRole,
isLoading,
getTemplateStoryList,
actionStory,
setSelectedTemplate,
setActiveRoleIndex,
setActiveRoleImage,
setActiveRoleAudio,
} = useTemplateStoryServiceHook();
// 本地加载状态,用于 UI 反馈
const [localLoading, setLocalLoading] = useState(false);
// 组件挂载时获取模板列表
useEffect(() => {
if (isOpen) {
getTemplateStoryList();
}
}, [isOpen, getTemplateStoryList]);
// 处理模板选择
const handleTemplateSelect = (template: StoryTemplateEntity) => {
setSelectedTemplate(template);
setActiveRoleIndex(0); // 重置角色选择
};
// 处理确认操作
const handleConfirm = async () => {
if (!selectedTemplate) return;
try {
setLocalLoading(true);
const projectId = await actionStory();
console.log("Story action created:", projectId);
onClose();
// 重置状态
setSelectedTemplate(null);
setActiveRoleIndex(0);
} catch (error) {
console.error("Failed to create story action:", error);
// 这里可以添加 toast 提示
} finally {
setLocalLoading(false);
}
};
// 处理角色图片上传
const handleRoleImageUpload = (roleIndex: number, file: any) => {
if (file && selectedTemplate) {
// 模拟上传成功,设置图片URL
const imageUrl = URL.createObjectURL(file);
setActiveRoleImage(imageUrl);
}
};
// 删除角色图片
const handleDeleteRoleImage = (roleIndex: number) => {
if (selectedTemplate) {
setActiveRoleImage("");
}
};
// 模板列表渲染
const templateListRender = () => {
return (
Story Templates
{templateStoryList.map((template, index) => (
handleTemplateSelect(template)}
>
))}
);
};
// 故事编辑器渲染
const storyEditorRender = () => {
return isLoading ? (
) : selectedTemplate ? (
{/* 模板信息头部 - 增加顶部空间 */}
{/* 左侧图片 */}
{/* 右侧信息 - 增加文本渲染空间 */}
{selectedTemplate.name}
{selectedTemplate.generateText}
{/* 角色自定义部分 - 精简布局 */}
Character Customization
{/* 紧凑布局 */}
{/* 左侧:当前选中角色的音频与照片更改 - 精简版本 */}
{/* 图片上传部分 - 精简 */}
false}
onChange={(info) => {
if (info.file.status === "done") {
handleRoleImageUpload(
activeRoleIndex,
info.file.originFileObj
);
}
}}
>
{activeRole?.photo_url ? (
) : (
Upload Photo
)}
{/* 音频部分 - 精简版本 */}
{/* 音频操作区域 - 使用新的 AudioRecorder 组件 */}
{
setActiveRoleAudio(audioUrl);
}}
onAudioDeleted={() => {
setActiveRoleAudio("");
}}
/>
{/* 右侧:角色图片缩略图列表 - 精简 */}
Characters
{selectedTemplate.storyRole.map((role, index: number) => (
))}
{/* 弹窗底部操作 - 只保留 Action 按钮 */}
) : (
No templates available
Please try again later
);
};
return (
<>
{
// 清空所有选中的内容数据
setSelectedTemplate(null);
setActiveRoleIndex(0);
onClose();
}}
footer={null}
width="60%"
style={{ maxWidth: "800px" }}
className="template-modal"
closeIcon={
×
}
>
{/* 弹窗头部 */}
Template Story Selection
{templateListRender()}
{storyEditorRender()}
>
);
};
/**
* 视频工具面板组件
* 提供脚本输入和视频克隆两种模式,支持展开/收起功能
*/
export function ChatInputBox() {
// 控制面板展开/收起状态
const [isExpanded, setIsExpanded] = useState(false);
// 模板故事弹窗状态
const [isTemplateModalOpen, setIsTemplateModalOpen] = useState(false);
// 图片故事模式状态
const [isPhotoStoryMode, setIsPhotoStoryMode] = useState(false);
// 共享状态 - 需要在不同渲染函数间共享
const [script, setScript] = useState(""); // 用户输入的脚本内容
// 子组件引用,用于调用子组件方法
const photoStoryModeRef = useRef<{
enterPhotoStoryMode: () => void;
getStoryContent: () => string;
}>(null);
// 响应式管理输入框内容:当在图片故事模式下,从子组件获取故事内容
useEffect(() => {
if (isPhotoStoryMode && photoStoryModeRef.current) {
const storyContent = photoStoryModeRef.current.getStoryContent();
if (storyContent) {
setScript(storyContent);
}
}
}, [isPhotoStoryMode]);
const [loadingIdea, setLoadingIdea] = useState(false); // 获取创意建议时的加载状态
const [isCreating, setIsCreating] = useState(false); // 视频创建过程中的加载状态
// 配置选项状态 - 整合所有配置项到一个对象
const [configOptions, setConfigOptions] = useState({
mode: "auto",
resolution: "720p",
language: "english",
videoDuration: "1min",
});
// 退出图片故事模式
const exitPhotoStoryMode = () => {
setIsPhotoStoryMode(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 editor content changes
const handleEditorChange = (e: React.FormEvent) => {
const newText = e.currentTarget.textContent || "";
setScript(newText);
};
// 当进入图片故事模式时,调用子组件方法
const enterPhotoStoryMode = () => {
setIsPhotoStoryMode(true);
// 调用子组件的进入方法
if (photoStoryModeRef.current) {
photoStoryModeRef.current.enterPhotoStoryMode();
}
};
// Handle creating video
const handleCreateVideo = async () => {
setIsCreating(true);
// 这里可以添加实际的创建逻辑
console.log("创建视频:", {
script,
...configOptions,
...(isPhotoStoryMode && {
// 从子组件获取图片故事相关信息
isPhotoStoryMode: true,
}),
});
setTimeout(() => {
setIsCreating(false);
}, 2000);
};
return (
{/* 视频故事板工具面板 - 毛玻璃效果背景 */}
{/* 展开/收起控制区域 */}
{isExpanded ? (
// 展开状态:显示收起按钮和提示
setIsExpanded(false)}
>
Click to action
) : (
// 收起状态:显示展开按钮
setIsExpanded(true)}
>
)}
{/* 主要内容区域 - 简化层级,垂直居中 */}
{/* 输入框和Action按钮 - 只在展开状态显示 */}
{!isExpanded && (
{/* 模式选择下拉菜单 */}
Template Story
),
},
{
key: "photo",
label: (
Photo Story
),
},
],
onClick: ({ key }) => {
if (key === "template") {
setIsTemplateModalOpen(true);
} else if (key === "photo") {
enterPhotoStoryMode();
}
console.log("Selected mode:", key);
},
}}
trigger={["click"]}
placement="topLeft"
overlayClassName="mode-dropdown"
>
{/* 图片故事模式UI - 始终渲染,通过ref调用方法 */}
{/* 可编辑的脚本输入区域 */}
{script}
{/* 占位符文本和获取创意按钮 */}
Describe the content you want to action. Get an
handleGetIdea()}
>
{loadingIdea ? (
) : (
<>
idea
>
)}
{/* 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 },
],
},
{
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="bottomLeft"
>
);
})}
);
};
/**
* 图片故事模式组件
* 显示图片预览、分析状态和故事类型选择器
* 使用自己的hook管理状态,通过ref暴露方法给父组件
*/
const PhotoStoryMode = forwardRef<
{
enterPhotoStoryMode: () => void;
getStoryContent: () => string;
},
{
onExitMode: () => void;
isVisible: boolean;
}
>(({ onExitMode, isVisible }, ref) => {
// 使用图片故事服务hook管理自己的状态
const {
activeImageUrl,
selectedCategory,
isAnalyzing,
isUploading,
storyTypeOptions,
analyzedStoryContent,
updateStoryType,
resetImageStory,
updateStoryContent,
triggerFileSelectionAndAnalyze,
} = useImageStoryServiceHook();
// 通过ref暴露方法给父组件
useImperativeHandle(
ref,
() => ({
enterPhotoStoryMode: () => {
// 触发文件选择和分析
triggerFileSelectionAndAnalyze().catch((error: unknown) => {
console.error("Failed to enter photo story mode:", error);
});
},
getStoryContent: () => analyzedStoryContent || "",
}),
[triggerFileSelectionAndAnalyze, analyzedStoryContent]
);
// 处理退出模式
const handleExitMode = () => {
resetImageStory();
onExitMode();
};
// 如果不显示,返回null
if (!isVisible) {
return null;
}
return (
{/* 左侧:图片预览区域和分析状态指示器 */}
{/* 图片预览区域 - 使用Ant Design Image组件 */}
{activeImageUrl && (
,
maskClassName:
"flex items-center justify-center bg-black/50 hover:bg-black/70 transition-colors",
}}
/>
)}
{/* 删除图片按钮 - 简洁样式 */}
{activeImageUrl && (
)}
{/* 分析状态指示器 */}
{isAnalyzing && (
{isUploading ? "Uploading image..." : "Analyzing image..."}
)}
{/* 右侧:故事类型选择器 */}
{activeImageUrl && (
({
key: type.key,
label: (
{type.label}
),
})),
onClick: ({ key }) => updateStoryType(key),
}}
trigger={["click"]}
placement="bottomRight"
>
)}
);
});