forked from 77media/video-flow
修复 action 无法禁用 问题
This commit is contained in:
parent
b7d036157b
commit
1145ee13d3
@ -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>
|
||||
</>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user