diff --git a/app/service/Interaction/templateStoryService.ts b/app/service/Interaction/templateStoryService.ts index 5e20e69..e159cb8 100644 --- a/app/service/Interaction/templateStoryService.ts +++ b/app/service/Interaction/templateStoryService.ts @@ -70,21 +70,24 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => { { id: '1', name: '奇幻冒险故事', - imageUrl: ['/assets/3dr_howlcastle.png', '/assets/3dr_spirited.jpg'], + image_url: ['/assets/3dr_howlcastle.png', '/assets/3dr_spirited.jpg'], generateText: '一个关于勇气与友谊的奇幻冒险故事,主角在神秘世界中寻找失落的宝藏,结识了各种奇特的生物和伙伴。', storyRole: [ { role_name: '艾莉娅', + role_description: '一个勇敢的女孩,拥有强大的魔法力量,她的冒险旅程充满了危险和挑战。', photo_url: '/assets/3dr_chihiro.png', voice_url:"" }, { role_name: '魔法师梅林', + role_description: '一个智慧的魔法师,拥有强大的魔法力量,他的冒险旅程充满了危险和挑战。', photo_url: '/assets/3dr_mono.png', voice_url:"" }, { role_name: '守护者龙', + role_description: '一个强大的守护者,拥有强大的魔法力量,他的冒险旅程充满了危险和挑战。', photo_url: '/assets/3dr_howlbg.jpg', voice_url:"" } @@ -93,39 +96,44 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => { { id: '2', name: '科幻探索之旅', - imageUrl: ['/assets/3dr_monobg.jpg'], + image_url: ['/assets/3dr_monobg.jpg'], generateText: '未来世界的太空探索故事,人类在浩瀚宇宙中寻找新的家园,面对未知的挑战和外星文明的接触。', storyRole: [ { role_name: '船长凯特', + role_description: '一个勇敢的船长,拥有强大的魔法力量,他的冒险旅程充满了危险和挑战。', photo_url: '/assets/3dr_chihiro.png', voice_url:"" }, { role_name: 'AI助手诺娃', + role_description: '一个强大的AI助手,拥有强大的魔法力量,他的冒险旅程充满了危险和挑战。', photo_url: '/assets/3dr_mono.png', - voice_url:"" + voice_url: '' } ] }, { id: '3', name: '温馨家庭喜剧', - imageUrl: ['/assets/3dr_spirited.jpg'], + image_url: ['/assets/3dr_spirited.jpg'], generateText: '一个充满欢笑和温情的家庭故事,讲述家庭成员之间的日常趣事,以及如何一起面对生活中的小挑战。', storyRole: [ { role_name: '妈妈莉莉', + role_description: '一个温柔的妈妈,拥有强大的魔法力量,她的冒险旅程充满了危险和挑战。', photo_url: '/assets/3dr_chihiro.png', voice_url:"" }, { role_name: '爸爸汤姆', + role_description: '一个勇敢的爸爸,拥有强大的魔法力量,他的冒险旅程充满了危险和挑战。', photo_url: '/assets/3dr_mono.png', voice_url:"" }, { role_name: '孩子小杰', + role_description: '一个聪明的孩子,拥有强大的魔法力量,他的冒险旅程充满了危险和挑战。', photo_url: '/assets/3dr_howlbg.jpg', voice_url:"" } diff --git a/app/service/domain/Entities.ts b/app/service/domain/Entities.ts index d7139f5..f8c5856 100644 --- a/app/service/domain/Entities.ts +++ b/app/service/domain/Entities.ts @@ -137,13 +137,15 @@ export interface StoryTemplateEntity { /** 故事模板名称 */ name: string; /** 故事模板图片 */ - imageUrl: string[]; + image_url: string[]; /** 故事模板概览*/ generateText: string; /**故事角色 */ storyRole: { /**角色名 */ role_name: string; + /**角色描述 */ + role_description: string; /**照片URL */ photo_url: string; /**声音URL */ diff --git a/components/ChatInputBox/ChatInputBox.tsx b/components/ChatInputBox/ChatInputBox.tsx index 61f12c4..0f167d8 100644 --- a/components/ChatInputBox/ChatInputBox.tsx +++ b/components/ChatInputBox/ChatInputBox.tsx @@ -18,7 +18,7 @@ import { Sparkles, Settings, } from "lucide-react"; -import { Dropdown, Modal, Tooltip, Upload, Spin } from "antd"; +import { Dropdown, Modal, Tooltip, Upload, Spin, Popconfirm } from "antd"; import { UploadOutlined } from "@ant-design/icons"; import { StoryTemplateEntity } from "@/app/service/domain/Entities"; import { useImageStoryServiceHook } from "@/app/service/Interaction/ImageStoryService"; @@ -30,6 +30,7 @@ import { createMovieProjectV1 } from "@/api/video_flow"; 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 = ({ @@ -55,7 +56,7 @@ const RenderTemplateStoryMode = ({ } = useTemplateStoryServiceHook(); // 本地加载状态,用于 UI 反馈 - const [localLoading, setLocalLoading] = useState(false); + const [localLoading, setLocalLoading] = useState(0); // 组件挂载时获取模板列表 useEffect(() => { @@ -73,9 +74,19 @@ const RenderTemplateStoryMode = ({ // 处理确认操作 const handleConfirm = async () => { if (!selectedTemplate) return; - + let timer = setInterval(() => { + setLocalLoading((prev) => { + if (prev >= 95) { + clearInterval(timer); + return 95; + } + return prev + 1; + }); + }, 100); try { - setLocalLoading(true); + setLocalLoading(1); + // 假性的增加进度条 + const projectId = await actionStory(); console.log("Story action created:", projectId); onClose(); @@ -86,7 +97,8 @@ const RenderTemplateStoryMode = ({ console.error("Failed to create story action:", error); // 这里可以添加 toast 提示 } finally { - setLocalLoading(false); + setLocalLoading(0); + clearInterval(timer); } }; @@ -119,7 +131,7 @@ const RenderTemplateStoryMode = ({ onClick={() => handleTemplateSelect(template)} > {selectedTemplate.name} @@ -322,7 +334,7 @@ const RenderTemplateStoryMode = ({ */}
0} handleCreateVideo={handleConfirm} icon={} /> @@ -775,15 +787,21 @@ const PhotoStoryModal = ({ uploadCharacterAvatarAndAnalyzeFeatures, } = useImageStoryServiceHook(); const { loadingText } = useLoadScriptText(isLoading); - const { uploadFile } = useUploadFile(); + const [localLoading, setLocalLoading] = useState(0); // 重置状态 const handleClose = () => { - resetImageStory(); + // resetImageStory(); onClose(); }; const router = useRouter(); // 处理图片上传 - const handleImageUpload = async () => { + 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) { @@ -820,6 +838,35 @@ const PhotoStoryModal = ({ } }; + const handleAnalyzeImage = async () => { + let timeout = 100 + let timer: NodeJS.Timeout; + timer = setInterval(() => { + setLocalLoading((prev) => { + if (prev >= 95) { + return 95; + } + return prev + 0.1; + }); + }, timeout); + try { + await uploadAndAnalyzeImage(); + } finally { + timeout=10 + clearInterval(timer); + timer = setInterval(() => { + setLocalLoading((prev) => { + if (prev >= 100) { + clearInterval(timer); + return 0; + } + return prev + 1; + }); + }, timeout); + + } + }; + return ( } > - + 0} progress={localLoading}>
{/* 弹窗头部 */}
@@ -865,18 +912,33 @@ const PhotoStoryModal = ({ alt="Story inspiration" className="w-full h-full object-cover rounded-lg" /> - + { + 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", + }} + > - +
) : (
@@ -1035,7 +1097,7 @@ const PhotoStoryModal = ({ > } /> @@ -1055,7 +1117,7 @@ const PhotoStoryModal = ({
- + ); }; diff --git a/components/common/GlobalLoad.tsx b/components/common/GlobalLoad.tsx new file mode 100644 index 0000000..2380782 --- /dev/null +++ b/components/common/GlobalLoad.tsx @@ -0,0 +1,138 @@ +import { Spin } from "antd"; +import { ReactNode } from "react"; + +interface GlobalLoadProps { + /** 是否显示加载状态 */ + show: boolean; + /** 子元素内容 */ + children: ReactNode; + /** 进度条数值 0-100,非必须 */ + progress?: number; + /** 旋转圆环直径,非必须 */ + spinnerDiameter?: number; + /** 进度条宽度,非必须 */ + progressWidth?: number; + /** 是否显示旋转圆圈,默认显示 */ + showSpinner?: boolean; +} + +/** + * 全局加载组件,支持标签包裹形式渲染子元素 + * @param props - 组件属性 + * @returns 加载组件 + */ +export default function GlobalLoad({ + show, + children, + progress, + spinnerDiameter, + progressWidth, + showSpinner = true, +}: GlobalLoadProps) { + if (!show) { + return <>{children}; + } + + // 自定义加载图标:组合两个组件 + const customIndicator = ( +
+ {showSpinner && } + {progress && } +
+ ); + + return ( +
+ + {children} + +
+ ); +} + +/** + * Tailwind CSS loading spinner component for global loading overlay. + * @param diameter - Diameter of the spinner in pixels + * @returns {JSX.Element} - Spinner visual. + */ +export const TailwindSpinner = ({ diameter = 50 }: { diameter?: number }) => { + const radius = diameter / 2; + + return ( +
+ {/* 主旋转圆环 */} +
+ {/* 背景模糊圆环 */} +
+ {/* 自定义动画关键帧 */} + +
+ ); +}; + +/** + * Tailwind CSS linear progress bar loader with animated light sweep. + * @param progress - Progress value from 0 to 100 + * @param width - Width of the progress bar in pixels + * @returns {JSX.Element} - Linear loader visual. + */ +export const TailwindLinearLoader = ({ + progress, + width = 160 +}: { + progress: number; + width?: number; +}) => ( +
+ {/* Animated light sweep - position controlled by progress */} +
+ {/* Keyframes for light sweep - no longer needed since position is controlled by progress */} +
+);