修复 action 无法禁用 问题

This commit is contained in:
北枳 2025-09-03 12:03:46 +08:00
parent b7d036157b
commit 1145ee13d3
3 changed files with 126 additions and 43 deletions

View File

@ -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 = ({
<div className="absolute right-2 top-1/2 -translate-y-1/2">
{/* AI生成按钮 */}
<ActionButton
isCreating={false}
handleCreateVideo={() => {
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={<Sparkles className="w-4 h-4" />}
width="w-8"
height="h-8"
disabled={isRoleGenerating[role.role_name] || false}
/>
</div>
</div>
@ -466,16 +496,21 @@ const RenderTemplateStoryMode = ({
<div className="absolute right-2 top-1/2 -translate-y-1/2">
{/* AI生成按钮 */}
<ActionButton
isCreating={false}
handleCreateVideo={() => {
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={<Sparkles className="w-4 h-4" />}
width="w-8"
height="h-8"
disabled={isItemGenerating[item.item_name] || false}
/>
</div>
</div>
@ -721,9 +757,10 @@ const RenderTemplateStoryMode = ({
</div> */}
<div className=" absolute -bottom-8 right-0">
<ActionButton
isCreating={localLoading > 0}
isCreating={isTemplateCreating || localLoading > 0}
handleCreateVideo={handleConfirm}
icon={<Clapperboard className="w-5 h-5" />}
disabled={isTemplateCreating || localLoading > 0}
/>
</div>
</div>
@ -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}
/>
</div>
@ -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}
/>
</div>
);
@ -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"
>
<ActionButton
isCreating={isLoading}
isCreating={isLoading || isPhotoCreating}
handleCreateVideo={handleAnalyzeImage}
icon={<Sparkles className="w-5 h-5" />}
disabled={isLoading || isPhotoCreating}
/>
</Tooltip>
) : (
@ -1563,6 +1643,7 @@ const PhotoStoryModal = ({
isCreating={isLoading}
handleCreateVideo={handleConfirm}
icon={<Clapperboard className="w-5 h-5" />}
disabled={isCreating}
/>
</Tooltip>
</>

View File

@ -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 (
<div className={`relative group ${className}`}>
<div
data-alt="action-button"
className={`relative ${width} ${height} opacity-90 cursor-pointer overflow-hidden rounded-xl bg-black z-10`}
onClick={isCreating ? undefined : handleCreateVideo}
>
<div className={`absolute z-10 -translate-x-12 group-hover:translate-x-12 transition-all duration-700 h-full ${width} bg-gradient-to-r from-gray-500 to-white/10 opacity-30 -skew-x-12`}></div>
<div className="absolute flex items-center justify-center text-white z-[1] opacity-90 rounded-xl inset-0.5 bg-black">
<button
name="text"
className="w-full h-full opacity-90 rounded-xl bg-black flex items-center justify-center"
className="w-full h-full opacity-90 rounded-xl bg-black flex items-center justify-center disabled:cursor-not-allowed"
onClick={isCreating ? undefined : handleCreateVideo}
disabled={isCreating || disabled}
>
{isCreating ? <Loader2 className="w-5 h-5 animate-spin" /> : icon}
</button>

View File

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