From 1145ee13d3bddf92de278c5439fa8872257fa582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8C=97=E6=9E=B3?= <7854742+wang_rumeng@user.noreply.gitee.com> Date: Wed, 3 Sep 2025 12:03:46 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20action=20=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E7=A6=81=E7=94=A8=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ChatInputBox/ChatInputBox.tsx | 161 +++++++++++++++++------ components/common/ActionButton.tsx | 7 +- components/pages/create-to-video2.tsx | 1 - 3 files changed, 126 insertions(+), 43 deletions(-) diff --git a/components/ChatInputBox/ChatInputBox.tsx b/components/ChatInputBox/ChatInputBox.tsx index 8e4b474..c32b6e0 100644 --- a/components/ChatInputBox/ChatInputBox.tsx +++ b/components/ChatInputBox/ChatInputBox.tsx @@ -47,6 +47,12 @@ import GlobalLoad from "../common/GlobalLoad"; /**模板故事模式弹窗组件 */ const RenderTemplateStoryMode = ({ + isTemplateCreating, + setIsTemplateCreating, + isRoleGenerating, + setIsRoleGenerating, + isItemGenerating, + setIsItemGenerating, isOpen, onClose, configOptions = { @@ -58,6 +64,12 @@ const RenderTemplateStoryMode = ({ }: { isOpen: boolean; onClose: () => void; + isTemplateCreating: boolean; + setIsTemplateCreating: (value: boolean) => void; + isRoleGenerating: { [key: string]: boolean }; + setIsRoleGenerating: (value: { [key: string]: boolean } | ((prev: { [key: string]: boolean }) => { [key: string]: boolean })) => void; + isItemGenerating: { [key: string]: boolean }; + setIsItemGenerating: (value: { [key: string]: boolean } | ((prev: { [key: string]: boolean }) => { [key: string]: boolean })) => void; configOptions: { mode: "auto" | "manual"; resolution: "720p" | "1080p" | "4k"; @@ -125,17 +137,12 @@ const RenderTemplateStoryMode = ({ // 处理确认操作 const handleConfirm = async () => { if (!selectedTemplate) return; - let timer = setInterval(() => { - setLocalLoading((prev) => { - if (prev >= 95) { - clearInterval(timer); - return 95; - } - return prev + 0.1; - }); - }, 100); + if (isTemplateCreating) return; + + setIsTemplateCreating(true); + let timer: NodeJS.Timeout | null = null; + try { - setLocalLoading(1); // 获取当前用户信息 const User = JSON.parse(localStorage.getItem("currentUser") || "{}"); @@ -143,12 +150,26 @@ const RenderTemplateStoryMode = ({ console.error("用户未登录"); return; } + + // 启动进度条动画 + timer = setInterval(() => { + setLocalLoading((prev) => { + if (prev >= 95) { + return 95; + } + return prev + 0.1; + }); + }, 100); + + setLocalLoading(1); + const projectId = await actionStory( String(User.id), configOptions.mode, configOptions.resolution, configOptions.language ); + if (projectId) { // 跳转到电影详情页 router.push(`/movies/work-flow?episodeId=${projectId}`); @@ -159,13 +180,16 @@ const RenderTemplateStoryMode = ({ console.log("Story action created:", projectId); } catch (error) { console.error("Failed to create story action:", error); + setIsTemplateCreating(false); // 这里可以添加 toast 提示 onClose(); // 重置状态 setSelectedTemplate(null); } finally { setLocalLoading(0); - clearInterval(timer); + if (timer) { + clearInterval(timer); + } } }; // 模板列表渲染 @@ -281,16 +305,21 @@ const RenderTemplateStoryMode = ({
{/* AI生成按钮 */} { + isCreating={isRoleGenerating[role.role_name] || false} + handleCreateVideo={async () => { if ( role.role_description && role.role_description.trim() ) { - handleRoleFieldBlur( - role.role_name, - role.role_description.trim() - ); + setIsRoleGenerating(prev => ({...prev, [role.role_name]: true})); + try { + await handleRoleFieldBlur( + role.role_name, + role.role_description.trim() + ); + } finally { + setIsRoleGenerating(prev => ({...prev, [role.role_name]: false})); + } } setInputVisible((prev) => ({ ...prev, @@ -300,6 +329,7 @@ const RenderTemplateStoryMode = ({ icon={} width="w-8" height="h-8" + disabled={isRoleGenerating[role.role_name] || false} />
@@ -466,16 +496,21 @@ const RenderTemplateStoryMode = ({
{/* AI生成按钮 */} { + isCreating={isItemGenerating[item.item_name] || false} + handleCreateVideo={async () => { if ( item.item_description && item.item_description.trim() ) { - handleItemFieldBlur( - item.item_name, - item.item_description.trim() - ); + setIsItemGenerating(prev => ({...prev, [item.item_name]: true})); + try { + await handleItemFieldBlur( + item.item_name, + item.item_description.trim() + ); + } finally { + setIsItemGenerating(prev => ({...prev, [item.item_name]: false})); + } } setInputVisible((prev) => ({ ...prev, @@ -485,6 +520,7 @@ const RenderTemplateStoryMode = ({ icon={} width="w-8" height="h-8" + disabled={isItemGenerating[item.item_name] || false} />
@@ -721,9 +757,10 @@ const RenderTemplateStoryMode = ({ */}
0} + isCreating={isTemplateCreating || localLoading > 0} handleCreateVideo={handleConfirm} icon={} + disabled={isTemplateCreating || localLoading > 0} />
@@ -798,7 +835,12 @@ export function ChatInputBox({ noData }: { noData: boolean }) { const router = useRouter(); const [loadingIdea, setLoadingIdea] = useState(false); // 获取创意建议时的加载状态 - const [isCreating, setIsCreating] = useState(false); // 视频创建过程中的加载状态 + // 各种操作的加载状态 + const [isCreating, setIsCreating] = useState(false); // 主视频创建按钮的加载状态 + const [isTemplateCreating, setIsTemplateCreating] = useState(false); // 模板故事创建按钮的加载状态 + const [isPhotoCreating, setIsPhotoCreating] = useState(false); // 图片故事创建按钮的加载状态 + const [isRoleGenerating, setIsRoleGenerating] = useState<{[key: string]: boolean}>({}); // 角色AI生成按钮的加载状态 + const [isItemGenerating, setIsItemGenerating] = useState<{[key: string]: boolean}>({}); // 道具AI生成按钮的加载状态 // 配置选项状态 - 整合所有配置项到一个对象 const [configOptions, setConfigOptions] = useState<{ @@ -848,35 +890,46 @@ export function ChatInputBox({ noData }: { noData: boolean }) { // Handle creating video const handleCreateVideo = async () => { - setIsCreating(true); + if (isCreating) return; // 如果正在创建中,直接返回 + if (!script) { - setIsCreating(false); + console.warn("请输入剧本内容"); 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, - }; + setIsCreating(true); // 设置创建状态为 true,按钮会显示 loading 状态 - // 调用创建剧集API try { + const User = JSON.parse(localStorage.getItem("currentUser") || "{}"); + + if (!User.id) { + console.error("用户未登录"); + return; + } + + // 创建剧集数据 + let episodeData: any = { + user_id: String(User.id), + script: script, + mode: configOptions.mode, + resolution: configOptions.resolution, + language: configOptions.language, + video_duration: configOptions.videoDuration, + }; + + // 调用创建剧集API const result = await MovieProjectService.createProject( MovieProjectMode.NORMAL, episodeData ); + const episodeId = result.project_id; router.push(`/movies/work-flow?episodeId=${episodeId}`); + } catch (error) { console.error("创建剧集失败:", error); } finally { - setIsCreating(false); + setIsCreating(false); // 无论成功还是失败,都重置创建状态 } }; @@ -1048,6 +1101,10 @@ export function ChatInputBox({ noData }: { noData: boolean }) { isOpen={isPhotoStoryModalOpen} onClose={() => setIsPhotoStoryModalOpen(false)} configOptions={configOptions} + isCreating={isCreating} + setIsCreating={setIsCreating} + isPhotoCreating={isPhotoCreating} + setIsPhotoCreating={setIsPhotoCreating} /> @@ -1077,6 +1134,12 @@ export function ChatInputBox({ noData }: { noData: boolean }) { configOptions={configOptions} isOpen={isTemplateModalOpen} onClose={() => setIsTemplateModalOpen(false)} + isTemplateCreating={isTemplateCreating} + setIsTemplateCreating={setIsTemplateCreating} + isRoleGenerating={isRoleGenerating} + setIsRoleGenerating={setIsRoleGenerating} + isItemGenerating={isItemGenerating} + setIsItemGenerating={setIsItemGenerating} /> ); @@ -1196,6 +1259,10 @@ const ConfigOptions = ({ * 提供图片上传、AI分析和故事生成功能,支持动态UI变化 */ const PhotoStoryModal = ({ + isCreating, + setIsCreating, + isPhotoCreating, + setIsPhotoCreating, isOpen, onClose, configOptions = { @@ -1207,6 +1274,10 @@ const PhotoStoryModal = ({ }: { isOpen: boolean; onClose: () => void; + isCreating: boolean; + setIsCreating: (value: boolean) => void; + isPhotoCreating: boolean; + setIsPhotoCreating: (value: boolean) => void; configOptions?: { mode: "auto" | "manual"; resolution: "720p" | "1080p" | "4k"; @@ -1272,6 +1343,7 @@ const PhotoStoryModal = ({ // 处理确认 const handleConfirm = async () => { try { + setIsCreating(true); // 获取当前用户信息 const User = JSON.parse(localStorage.getItem("currentUser") || "{}"); @@ -1294,11 +1366,15 @@ const PhotoStoryModal = ({ // 成功后关闭弹窗 handleClose(); } catch (error) { + setIsCreating(false); console.error("创建电影项目失败:", error); } }; const handleAnalyzeImage = async () => { + if (isPhotoCreating || isLoading) return; + + setIsPhotoCreating(true); let timeout = 100; let timer: NodeJS.Timeout; timer = setInterval(() => { @@ -1312,6 +1388,9 @@ const PhotoStoryModal = ({ }, timeout); try { await uploadAndAnalyzeImage(); + } catch (error) { + console.error("分析图片失败:", error); + setIsPhotoCreating(false); } finally { clearInterval(timer); setLocalLoading(0); @@ -1550,9 +1629,10 @@ const PhotoStoryModal = ({ placement="top" > } + disabled={isLoading || isPhotoCreating} /> ) : ( @@ -1563,6 +1643,7 @@ const PhotoStoryModal = ({ isCreating={isLoading} handleCreateVideo={handleConfirm} icon={} + disabled={isCreating} /> diff --git a/components/common/ActionButton.tsx b/components/common/ActionButton.tsx index 58b130b..b36b9ee 100644 --- a/components/common/ActionButton.tsx +++ b/components/common/ActionButton.tsx @@ -8,6 +8,7 @@ export function ActionButton({ width = "w-12", height = "h-12", className = "", + disabled = false, }: { isCreating: boolean; handleCreateVideo: () => void; @@ -15,19 +16,21 @@ export function ActionButton({ width?: string; height?: string; className?: string; + disabled?: boolean; }) { return (
diff --git a/components/pages/create-to-video2.tsx b/components/pages/create-to-video2.tsx index c72451a..8c70541 100644 --- a/components/pages/create-to-video2.tsx +++ b/components/pages/create-to-video2.tsx @@ -6,7 +6,6 @@ import { useRouter, useSearchParams } from 'next/navigation'; import './style/create-to-video2.css'; import { getScriptEpisodeListNew } from "@/api/script_episode"; -import { EmptyStateAnimation } from '@/components/common/EmptyStateAnimation2'; import { ChatInputBox } from '@/components/ChatInputBox/ChatInputBox'; import cover_image1 from '@/public/assets/cover_image1.jpg'; import { motion } from 'framer-motion';