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';