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 = ({
|
const RenderTemplateStoryMode = ({
|
||||||
|
isTemplateCreating,
|
||||||
|
setIsTemplateCreating,
|
||||||
|
isRoleGenerating,
|
||||||
|
setIsRoleGenerating,
|
||||||
|
isItemGenerating,
|
||||||
|
setIsItemGenerating,
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
configOptions = {
|
configOptions = {
|
||||||
@ -58,6 +64,12 @@ const RenderTemplateStoryMode = ({
|
|||||||
}: {
|
}: {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
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: {
|
configOptions: {
|
||||||
mode: "auto" | "manual";
|
mode: "auto" | "manual";
|
||||||
resolution: "720p" | "1080p" | "4k";
|
resolution: "720p" | "1080p" | "4k";
|
||||||
@ -125,17 +137,12 @@ const RenderTemplateStoryMode = ({
|
|||||||
// 处理确认操作
|
// 处理确认操作
|
||||||
const handleConfirm = async () => {
|
const handleConfirm = async () => {
|
||||||
if (!selectedTemplate) return;
|
if (!selectedTemplate) return;
|
||||||
let timer = setInterval(() => {
|
if (isTemplateCreating) return;
|
||||||
setLocalLoading((prev) => {
|
|
||||||
if (prev >= 95) {
|
setIsTemplateCreating(true);
|
||||||
clearInterval(timer);
|
let timer: NodeJS.Timeout | null = null;
|
||||||
return 95;
|
|
||||||
}
|
|
||||||
return prev + 0.1;
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
try {
|
try {
|
||||||
setLocalLoading(1);
|
|
||||||
// 获取当前用户信息
|
// 获取当前用户信息
|
||||||
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
|
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
|
||||||
|
|
||||||
@ -143,12 +150,26 @@ const RenderTemplateStoryMode = ({
|
|||||||
console.error("用户未登录");
|
console.error("用户未登录");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 启动进度条动画
|
||||||
|
timer = setInterval(() => {
|
||||||
|
setLocalLoading((prev) => {
|
||||||
|
if (prev >= 95) {
|
||||||
|
return 95;
|
||||||
|
}
|
||||||
|
return prev + 0.1;
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
setLocalLoading(1);
|
||||||
|
|
||||||
const projectId = await actionStory(
|
const projectId = await actionStory(
|
||||||
String(User.id),
|
String(User.id),
|
||||||
configOptions.mode,
|
configOptions.mode,
|
||||||
configOptions.resolution,
|
configOptions.resolution,
|
||||||
configOptions.language
|
configOptions.language
|
||||||
);
|
);
|
||||||
|
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
// 跳转到电影详情页
|
// 跳转到电影详情页
|
||||||
router.push(`/movies/work-flow?episodeId=${projectId}`);
|
router.push(`/movies/work-flow?episodeId=${projectId}`);
|
||||||
@ -159,14 +180,17 @@ const RenderTemplateStoryMode = ({
|
|||||||
console.log("Story action created:", projectId);
|
console.log("Story action created:", projectId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to create story action:", error);
|
console.error("Failed to create story action:", error);
|
||||||
|
setIsTemplateCreating(false);
|
||||||
// 这里可以添加 toast 提示
|
// 这里可以添加 toast 提示
|
||||||
onClose();
|
onClose();
|
||||||
// 重置状态
|
// 重置状态
|
||||||
setSelectedTemplate(null);
|
setSelectedTemplate(null);
|
||||||
} finally {
|
} finally {
|
||||||
setLocalLoading(0);
|
setLocalLoading(0);
|
||||||
|
if (timer) {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// 模板列表渲染
|
// 模板列表渲染
|
||||||
const templateListRender = () => {
|
const templateListRender = () => {
|
||||||
@ -281,16 +305,21 @@ const RenderTemplateStoryMode = ({
|
|||||||
<div className="absolute right-2 top-1/2 -translate-y-1/2">
|
<div className="absolute right-2 top-1/2 -translate-y-1/2">
|
||||||
{/* AI生成按钮 */}
|
{/* AI生成按钮 */}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
isCreating={false}
|
isCreating={isRoleGenerating[role.role_name] || false}
|
||||||
handleCreateVideo={() => {
|
handleCreateVideo={async () => {
|
||||||
if (
|
if (
|
||||||
role.role_description &&
|
role.role_description &&
|
||||||
role.role_description.trim()
|
role.role_description.trim()
|
||||||
) {
|
) {
|
||||||
handleRoleFieldBlur(
|
setIsRoleGenerating(prev => ({...prev, [role.role_name]: true}));
|
||||||
|
try {
|
||||||
|
await handleRoleFieldBlur(
|
||||||
role.role_name,
|
role.role_name,
|
||||||
role.role_description.trim()
|
role.role_description.trim()
|
||||||
);
|
);
|
||||||
|
} finally {
|
||||||
|
setIsRoleGenerating(prev => ({...prev, [role.role_name]: false}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setInputVisible((prev) => ({
|
setInputVisible((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@ -300,6 +329,7 @@ const RenderTemplateStoryMode = ({
|
|||||||
icon={<Sparkles className="w-4 h-4" />}
|
icon={<Sparkles className="w-4 h-4" />}
|
||||||
width="w-8"
|
width="w-8"
|
||||||
height="h-8"
|
height="h-8"
|
||||||
|
disabled={isRoleGenerating[role.role_name] || false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -466,16 +496,21 @@ const RenderTemplateStoryMode = ({
|
|||||||
<div className="absolute right-2 top-1/2 -translate-y-1/2">
|
<div className="absolute right-2 top-1/2 -translate-y-1/2">
|
||||||
{/* AI生成按钮 */}
|
{/* AI生成按钮 */}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
isCreating={false}
|
isCreating={isItemGenerating[item.item_name] || false}
|
||||||
handleCreateVideo={() => {
|
handleCreateVideo={async () => {
|
||||||
if (
|
if (
|
||||||
item.item_description &&
|
item.item_description &&
|
||||||
item.item_description.trim()
|
item.item_description.trim()
|
||||||
) {
|
) {
|
||||||
handleItemFieldBlur(
|
setIsItemGenerating(prev => ({...prev, [item.item_name]: true}));
|
||||||
|
try {
|
||||||
|
await handleItemFieldBlur(
|
||||||
item.item_name,
|
item.item_name,
|
||||||
item.item_description.trim()
|
item.item_description.trim()
|
||||||
);
|
);
|
||||||
|
} finally {
|
||||||
|
setIsItemGenerating(prev => ({...prev, [item.item_name]: false}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setInputVisible((prev) => ({
|
setInputVisible((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@ -485,6 +520,7 @@ const RenderTemplateStoryMode = ({
|
|||||||
icon={<Sparkles className="w-4 h-4" />}
|
icon={<Sparkles className="w-4 h-4" />}
|
||||||
width="w-8"
|
width="w-8"
|
||||||
height="h-8"
|
height="h-8"
|
||||||
|
disabled={isItemGenerating[item.item_name] || false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -721,9 +757,10 @@ const RenderTemplateStoryMode = ({
|
|||||||
</div> */}
|
</div> */}
|
||||||
<div className=" absolute -bottom-8 right-0">
|
<div className=" absolute -bottom-8 right-0">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
isCreating={localLoading > 0}
|
isCreating={isTemplateCreating || localLoading > 0}
|
||||||
handleCreateVideo={handleConfirm}
|
handleCreateVideo={handleConfirm}
|
||||||
icon={<Clapperboard className="w-5 h-5" />}
|
icon={<Clapperboard className="w-5 h-5" />}
|
||||||
|
disabled={isTemplateCreating || localLoading > 0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -798,7 +835,12 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [loadingIdea, setLoadingIdea] = useState(false); // 获取创意建议时的加载状态
|
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<{
|
const [configOptions, setConfigOptions] = useState<{
|
||||||
@ -848,13 +890,23 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
// Handle creating video
|
// Handle creating video
|
||||||
|
|
||||||
const handleCreateVideo = async () => {
|
const handleCreateVideo = async () => {
|
||||||
setIsCreating(true);
|
if (isCreating) return; // 如果正在创建中,直接返回
|
||||||
|
|
||||||
if (!script) {
|
if (!script) {
|
||||||
setIsCreating(false);
|
console.warn("请输入剧本内容");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsCreating(true); // 设置创建状态为 true,按钮会显示 loading 状态
|
||||||
|
|
||||||
|
try {
|
||||||
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
|
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
|
||||||
|
|
||||||
|
if (!User.id) {
|
||||||
|
console.error("用户未登录");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 创建剧集数据
|
// 创建剧集数据
|
||||||
let episodeData: any = {
|
let episodeData: any = {
|
||||||
user_id: String(User.id),
|
user_id: String(User.id),
|
||||||
@ -866,17 +918,18 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 调用创建剧集API
|
// 调用创建剧集API
|
||||||
try {
|
|
||||||
const result = await MovieProjectService.createProject(
|
const result = await MovieProjectService.createProject(
|
||||||
MovieProjectMode.NORMAL,
|
MovieProjectMode.NORMAL,
|
||||||
episodeData
|
episodeData
|
||||||
);
|
);
|
||||||
|
|
||||||
const episodeId = result.project_id;
|
const episodeId = result.project_id;
|
||||||
router.push(`/movies/work-flow?episodeId=${episodeId}`);
|
router.push(`/movies/work-flow?episodeId=${episodeId}`);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("创建剧集失败:", error);
|
console.error("创建剧集失败:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsCreating(false);
|
setIsCreating(false); // 无论成功还是失败,都重置创建状态
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1048,6 +1101,10 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
isOpen={isPhotoStoryModalOpen}
|
isOpen={isPhotoStoryModalOpen}
|
||||||
onClose={() => setIsPhotoStoryModalOpen(false)}
|
onClose={() => setIsPhotoStoryModalOpen(false)}
|
||||||
configOptions={configOptions}
|
configOptions={configOptions}
|
||||||
|
isCreating={isCreating}
|
||||||
|
setIsCreating={setIsCreating}
|
||||||
|
isPhotoCreating={isPhotoCreating}
|
||||||
|
setIsPhotoCreating={setIsPhotoCreating}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1077,6 +1134,12 @@ export function ChatInputBox({ noData }: { noData: boolean }) {
|
|||||||
configOptions={configOptions}
|
configOptions={configOptions}
|
||||||
isOpen={isTemplateModalOpen}
|
isOpen={isTemplateModalOpen}
|
||||||
onClose={() => setIsTemplateModalOpen(false)}
|
onClose={() => setIsTemplateModalOpen(false)}
|
||||||
|
isTemplateCreating={isTemplateCreating}
|
||||||
|
setIsTemplateCreating={setIsTemplateCreating}
|
||||||
|
isRoleGenerating={isRoleGenerating}
|
||||||
|
setIsRoleGenerating={setIsRoleGenerating}
|
||||||
|
isItemGenerating={isItemGenerating}
|
||||||
|
setIsItemGenerating={setIsItemGenerating}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -1196,6 +1259,10 @@ const ConfigOptions = ({
|
|||||||
* 提供图片上传、AI分析和故事生成功能,支持动态UI变化
|
* 提供图片上传、AI分析和故事生成功能,支持动态UI变化
|
||||||
*/
|
*/
|
||||||
const PhotoStoryModal = ({
|
const PhotoStoryModal = ({
|
||||||
|
isCreating,
|
||||||
|
setIsCreating,
|
||||||
|
isPhotoCreating,
|
||||||
|
setIsPhotoCreating,
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
configOptions = {
|
configOptions = {
|
||||||
@ -1207,6 +1274,10 @@ const PhotoStoryModal = ({
|
|||||||
}: {
|
}: {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
isCreating: boolean;
|
||||||
|
setIsCreating: (value: boolean) => void;
|
||||||
|
isPhotoCreating: boolean;
|
||||||
|
setIsPhotoCreating: (value: boolean) => void;
|
||||||
configOptions?: {
|
configOptions?: {
|
||||||
mode: "auto" | "manual";
|
mode: "auto" | "manual";
|
||||||
resolution: "720p" | "1080p" | "4k";
|
resolution: "720p" | "1080p" | "4k";
|
||||||
@ -1272,6 +1343,7 @@ const PhotoStoryModal = ({
|
|||||||
// 处理确认
|
// 处理确认
|
||||||
const handleConfirm = async () => {
|
const handleConfirm = async () => {
|
||||||
try {
|
try {
|
||||||
|
setIsCreating(true);
|
||||||
// 获取当前用户信息
|
// 获取当前用户信息
|
||||||
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
|
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
|
||||||
|
|
||||||
@ -1294,11 +1366,15 @@ const PhotoStoryModal = ({
|
|||||||
// 成功后关闭弹窗
|
// 成功后关闭弹窗
|
||||||
handleClose();
|
handleClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setIsCreating(false);
|
||||||
console.error("创建电影项目失败:", error);
|
console.error("创建电影项目失败:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAnalyzeImage = async () => {
|
const handleAnalyzeImage = async () => {
|
||||||
|
if (isPhotoCreating || isLoading) return;
|
||||||
|
|
||||||
|
setIsPhotoCreating(true);
|
||||||
let timeout = 100;
|
let timeout = 100;
|
||||||
let timer: NodeJS.Timeout;
|
let timer: NodeJS.Timeout;
|
||||||
timer = setInterval(() => {
|
timer = setInterval(() => {
|
||||||
@ -1312,6 +1388,9 @@ const PhotoStoryModal = ({
|
|||||||
}, timeout);
|
}, timeout);
|
||||||
try {
|
try {
|
||||||
await uploadAndAnalyzeImage();
|
await uploadAndAnalyzeImage();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("分析图片失败:", error);
|
||||||
|
setIsPhotoCreating(false);
|
||||||
} finally {
|
} finally {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
setLocalLoading(0);
|
setLocalLoading(0);
|
||||||
@ -1550,9 +1629,10 @@ const PhotoStoryModal = ({
|
|||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
isCreating={isLoading}
|
isCreating={isLoading || isPhotoCreating}
|
||||||
handleCreateVideo={handleAnalyzeImage}
|
handleCreateVideo={handleAnalyzeImage}
|
||||||
icon={<Sparkles className="w-5 h-5" />}
|
icon={<Sparkles className="w-5 h-5" />}
|
||||||
|
disabled={isLoading || isPhotoCreating}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
@ -1563,6 +1643,7 @@ const PhotoStoryModal = ({
|
|||||||
isCreating={isLoading}
|
isCreating={isLoading}
|
||||||
handleCreateVideo={handleConfirm}
|
handleCreateVideo={handleConfirm}
|
||||||
icon={<Clapperboard className="w-5 h-5" />}
|
icon={<Clapperboard className="w-5 h-5" />}
|
||||||
|
disabled={isCreating}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export function ActionButton({
|
|||||||
width = "w-12",
|
width = "w-12",
|
||||||
height = "h-12",
|
height = "h-12",
|
||||||
className = "",
|
className = "",
|
||||||
|
disabled = false,
|
||||||
}: {
|
}: {
|
||||||
isCreating: boolean;
|
isCreating: boolean;
|
||||||
handleCreateVideo: () => void;
|
handleCreateVideo: () => void;
|
||||||
@ -15,19 +16,21 @@ export function ActionButton({
|
|||||||
width?: string;
|
width?: string;
|
||||||
height?: string;
|
height?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={`relative group ${className}`}>
|
<div className={`relative group ${className}`}>
|
||||||
<div
|
<div
|
||||||
data-alt="action-button"
|
data-alt="action-button"
|
||||||
className={`relative ${width} ${height} opacity-90 cursor-pointer overflow-hidden rounded-xl bg-black z-10`}
|
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 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">
|
<div className="absolute flex items-center justify-center text-white z-[1] opacity-90 rounded-xl inset-0.5 bg-black">
|
||||||
<button
|
<button
|
||||||
name="text"
|
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}
|
{isCreating ? <Loader2 className="w-5 h-5 animate-spin" /> : icon}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { useRouter, useSearchParams } from 'next/navigation';
|
|||||||
import './style/create-to-video2.css';
|
import './style/create-to-video2.css';
|
||||||
|
|
||||||
import { getScriptEpisodeListNew } from "@/api/script_episode";
|
import { getScriptEpisodeListNew } from "@/api/script_episode";
|
||||||
import { EmptyStateAnimation } from '@/components/common/EmptyStateAnimation2';
|
|
||||||
import { ChatInputBox } from '@/components/ChatInputBox/ChatInputBox';
|
import { ChatInputBox } from '@/components/ChatInputBox/ChatInputBox';
|
||||||
import cover_image1 from '@/public/assets/cover_image1.jpg';
|
import cover_image1 from '@/public/assets/cover_image1.jpg';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user