diff --git a/app/service/Interaction/ImageStoryService.ts b/app/service/Interaction/ImageStoryService.ts index 9ee1c4d..7ec2a46 100644 --- a/app/service/Interaction/ImageStoryService.ts +++ b/app/service/Interaction/ImageStoryService.ts @@ -492,6 +492,7 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { } } catch (error) { console.error("创建电影项目失败:", error); + throw error; } }, [ diff --git a/app/service/Interaction/templateStoryService.ts b/app/service/Interaction/templateStoryService.ts index 7d58f7d..8fa1dc4 100644 --- a/app/service/Interaction/templateStoryService.ts +++ b/app/service/Interaction/templateStoryService.ts @@ -276,6 +276,7 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => { return result.project_id; } catch (error) { console.error("创建电影项目失败:", error); + throw error; } finally { // 清除 loading 状态 setIsLoading(false); diff --git a/components/ChatInputBox/ChatInputBox.tsx b/components/ChatInputBox/ChatInputBox.tsx index d236085..4a48075 100644 --- a/components/ChatInputBox/ChatInputBox.tsx +++ b/components/ChatInputBox/ChatInputBox.tsx @@ -44,6 +44,23 @@ import { useLoadScriptText, useUploadFile } from "@/app/service/domain/service"; import { ActionButton } from "../common/ActionButton"; import { HighlightEditor } from "../common/HighlightEditor"; import GlobalLoad from "../common/GlobalLoad"; +import { useDeviceType } from '@/hooks/useDeviceType'; +import { PcTemplateModal } from "./PcTemplateModal"; +import { H5TemplateDrawer } from "./H5TemplateDrawer"; +import { PcPhotoStoryModal } from "./PcPhotoStoryModal"; +import { H5PhotoStoryDrawer } from "./H5PhotoStoryDrawer"; + +const LauguageOptions = [ + { value: "english", label: "English", isVip: false, code:'en' }, + { value: "chinese", label: "Chinese", isVip: false, code:'zh' }, + { value: "japanese", label: "Japanese", isVip: false, code:'ja' }, + { value: "spanish", label: "Spanish", isVip: false, code:'es' }, + { value: "portuguese", label: "Portuguese", isVip: false, code:'pt' }, + { value: "hindi", label: "Hindi", isVip: false, code:'hi' }, + { value: "korean", label: "Korean", isVip: false, code:'ko' }, + { value: "arabic", label: "Arabic", isVip: false, code:'ar' }, + { value: "russian", label: "Russian", isVip: false, code:'ru' }, +] /**模板故事模式弹窗组件 */ /** @@ -64,850 +81,12 @@ const debounce = (func: Function, wait: number) => { }; }; -const RenderTemplateStoryMode = ({ - isTemplateCreating, - setIsTemplateCreating, - isRoleGenerating, - setIsRoleGenerating, - isItemGenerating, - setIsItemGenerating, - isOpen, - onClose, - configOptions = { - mode: "auto" as "auto" | "manual", - resolution: "720p" as "720p" | "1080p" | "4k", - language: "english", - videoDuration: "1min", - }, -}: { - 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"; - language: string; - videoDuration: string; - }; -}) => { - // 使用 hook 管理状态 - const { - templateStoryList, - selectedTemplate, - isLoading, - getTemplateStoryList, - actionStory, - setSelectedTemplate, - AvatarAndAnalyzeFeatures, - updateRoleImage, - updateItemImage, - handleRoleFieldBlur, - handleItemFieldBlur, - clearData, - } = useTemplateStoryServiceHook(); - - // 防抖处理的输入更新函数 - const debouncedUpdateInput = debounce((value: string) => { - // 过滤特殊字符 - const sanitizedValue = value.replace(/[<>]/g, ''); - // 更新输入值 - if (!selectedTemplate?.freeInputItem) return; - const updatedTemplate: StoryTemplateEntity = { - ...selectedTemplate, - freeInputItem: { - ...selectedTemplate.freeInputItem, - free_input_text: sanitizedValue - } - }; - setSelectedTemplate(updatedTemplate); - }, 300); // 300ms 的防抖延迟 - - // 使用上传文件hook - const { uploadFile, isUploading } = useUploadFile(); - // 本地加载状态,用于 UI 反馈 - const [localLoading, setLocalLoading] = useState(0); - // 控制输入框显示状态 - const [inputVisible, setInputVisible] = useState<{ [key: string]: boolean }>( - {} - ); - const router = useRouter(); - // 组件挂载时获取模板列表 - useEffect(() => { - if (isOpen) { - getTemplateStoryList(); - } - }, [isOpen, getTemplateStoryList]); - - // 监听点击外部区域关闭输入框 - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - const target = event.target as Element; - // 检查是否点击了输入框相关的元素 - if ( - !target.closest(".ant-tooltip") && - !target.closest('[data-alt*="field-ai-button"]') - ) { - // 关闭所有打开的输入框 - setInputVisible({}); - } - }; - - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, []); - - // 处理模板选择 - const handleTemplateSelect = (template: StoryTemplateEntity) => { - setSelectedTemplate(template); - }; - - // 处理确认操作 - const handleConfirm = async () => { - if (!selectedTemplate) return; - if (isTemplateCreating) return; - - setIsTemplateCreating(true); - let timer: NodeJS.Timeout | null = null; - - try { - // 获取当前用户信息 - const User = JSON.parse(localStorage.getItem("currentUser") || "{}"); - - if (!User.id) { - 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}`); - onClose(); - // 重置状态 - setSelectedTemplate(null); - } - 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); - if (timer) { - clearInterval(timer); - } - } - }; - // 模板列表渲染 - const templateListRender = () => { - return ( -
-
- {templateStoryList.map((template, index) => ( -
handleTemplateSelect(template)} - > - -
- ))} -
-
- ); - }; - // 故事编辑器渲染 - const storyEditorRender = () => { - return selectedTemplate ? ( -
- {/* 模板信息头部 - 增加顶部空间 */} -
- {/* 左侧图片 */} -
- {selectedTemplate.name} -
- - {/* 右侧信息 - 增加文本渲染空间 */} -
-

- {selectedTemplate.name} -

-
-

- {selectedTemplate.generateText} -

-
-
-
- - {/* 角色配置区域 */} - {selectedTemplate?.storyRole && - selectedTemplate.storyRole.length > 0 && ( -
-

- Character Configuration -

-
- {selectedTemplate.storyRole.map((role, index) => ( -
- {/* 图片容器 */} -
- - { - // 更新角色的描述字段 - const updatedTemplate = { - ...selectedTemplate!, - storyRole: selectedTemplate!.storyRole.map( - (r) => - r.role_name === role.role_name - ? { - ...r, - role_description: e.target.value, - } - : r - ), - }; - setSelectedTemplate(updatedTemplate); - }} - placeholder={role.user_tips} - className="w-[30rem] px-3 py-2 pr-16 bg-white/0 border border-white/10 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500/30 transition-all duration-200 text-sm" - /> -
- {/* AI生成按钮 */} - { - if ( - role.role_description && - 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, - [role.role_name]: false, - })); - }} - icon={} - width="w-8" - height="h-8" - disabled={isRoleGenerating[role.role_name] || false} - /> -
-
- } - placement="top" - classNames={{ - root: "max-w-none", - }} - open={inputVisible[role.role_name]} - onOpenChange={(visible) => - setInputVisible((prev) => ({ - ...prev, - [role.role_name]: visible, - })) - } - trigger="contextMenu" - styles={{ root: { zIndex: 1000 } }} - > - {/* 图片 */} -
- {role.role_name} -
- - - {/* 角色名称 - 图片下方 */} -
- - {role.role_name} - -
- - {/* 按钮组 - 右上角 */} -
- {/* AI生成按钮 */} - - - - - {/* 上传按钮 */} - { - const isImage = file.type.startsWith("image/"); - if (!isImage) { - console.error("只能上传图片文件"); - return false; - } - const isLt5M = file.size / 1024 / 1024 < 5; - if (!isLt5M) { - console.error("图片大小不能超过5MB"); - return false; - } - return true; - }} - customRequest={async ({ - file, - onSuccess, - onError, - }) => { - try { - const fileObj = file as File; - const uploadedUrl = await uploadFile( - fileObj, - (progress) => { - console.log(`上传进度: ${progress}%`); - } - ); - await AvatarAndAnalyzeFeatures( - uploadedUrl, - role.role_name - ); - onSuccess?.(uploadedUrl); - } catch (error) { - console.error("角色图片上传失败:", error); - onError?.(error as Error); - } - }} - > - - - - -
-
-
- ))} -
-
- )} - - {/* 道具配置区域 */} - {selectedTemplate?.storyItem && - selectedTemplate.storyItem.length > 0 && ( -
-

- props Configuration -

-
- {selectedTemplate.storyItem.map((item, index) => ( -
- {/* 图片容器 */} -
- - { - // 更新道具的描述字段 - const updatedTemplate = { - ...selectedTemplate!, - storyItem: selectedTemplate!.storyItem.map( - (i) => - i.item_name === item.item_name - ? { - ...i, - item_description: e.target.value, - } - : i - ), - }; - setSelectedTemplate(updatedTemplate); - }} - placeholder="Enter description for AI image generation..." - className="w-[30rem] px-3 py-2 pr-16 bg-white/0 border border-white/10 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500/30 transition-all duration-200 text-sm" - /> -
- {/* AI生成按钮 */} - { - if ( - item.item_description && - 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, - [item.item_name]: false, - })); - }} - icon={} - width="w-8" - height="h-8" - disabled={isItemGenerating[item.item_name] || false} - /> -
-
- } - placement="top" - classNames={{ - root: "max-w-none", - }} - open={inputVisible[item.item_name]} - onOpenChange={(visible) => - setInputVisible((prev) => ({ - ...prev, - [item.item_name]: visible, - })) - } - trigger="contextMenu" - styles={{ root: { zIndex: 1000 } }} - > - {/* 图片 */} -
- {item.item_name} -
- - - {/* 道具名称 - 图片下方 */} -
- - {item.item_name} - -
- - {/* 按钮组 - 右上角 */} -
- {/* AI生成按钮 */} - - - - - {/* 上传按钮 */} - { - const isImage = file.type.startsWith("image/"); - if (!isImage) { - console.error("只能上传图片文件"); - return false; - } - const isLt5M = file.size / 1024 / 1024 < 5; - if (!isLt5M) { - console.error("图片大小不能超过5MB"); - return false; - } - return true; - }} - customRequest={async ({ - file, - onSuccess, - onError, - }) => { - try { - const fileObj = file as File; - const uploadedUrl = await uploadFile( - fileObj, - (progress) => { - console.log(`上传进度: ${progress}%`); - } - ); - updateItemImage(item.item_name, uploadedUrl); - onSuccess?.(uploadedUrl); - } catch (error) { - console.error("道具图片上传失败:", error); - onError?.(error as Error); - } - }} - > - - - - -
-
-
- ))} -
- - )} - - {/* * 自由输入文字 - {(selectedTemplate?.freeInputItem) && ( -
-

- input Configuration -

-