diff --git a/app/service/Interaction/ImageStoryService.ts b/app/service/Interaction/ImageStoryService.ts index c05c2f3..748bfe7 100644 --- a/app/service/Interaction/ImageStoryService.ts +++ b/app/service/Interaction/ImageStoryService.ts @@ -1,4 +1,5 @@ import { ImageStoryEntity } from "../domain/Entities"; +import { useUploadFile } from "../domain/service"; import { ImageStoryUseCase } from "../usecase/imageStoryUseCase"; import { useState, useCallback, useMemo } from "react"; @@ -7,8 +8,8 @@ interface UseImageStoryService { imageStory: Partial; /** 当前活跃的图片地址 */ activeImageUrl: string; - /** 当前活跃的文本信息 */ - activeTextContent: string; + /** 分析故事结果内容 */ + analyzedStoryContent: string; /** 当前选中的分类 */ selectedCategory: string; /** 是否正在分析图片 */ @@ -19,6 +20,8 @@ interface UseImageStoryService { storyTypeOptions: Array<{ key: string; label: string }>; /** 上传图片并分析 */ uploadAndAnalyzeImage: (imageUrl: string) => Promise; + /** 触发文件选择并自动分析 */ + triggerFileSelectionAndAnalyze: () => Promise; /** 触发生成剧本函数 */ generateScript: () => Promise; /** 更新故事类型 */ @@ -29,7 +32,8 @@ interface UseImageStoryService { resetImageStory: () => void; } -export const useImageStoryServiceHook = (): UseImageStoryService => { +export const useImageStoryServiceHook = ( +): UseImageStoryService => { const [imageStory, setImageStory] = useState>({ imageUrl: "", imageStory: "", @@ -37,18 +41,20 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { }); const [isAnalyzing, setIsAnalyzing] = useState(false); const [isUploading, setIsUploading] = useState(false); + // 使用上传文件Hook + const { uploadFile } = useUploadFile(); /** 图片故事用例实例 */ const imageStoryUseCase = useMemo(() => new ImageStoryUseCase(), []); /** 当前活跃的图片地址 */ - const activeImageUrl = imageStory.imageUrl || ""; + const [activeImageUrl, setActiveImageUrl] = useState(""); - /** 当前活跃的文本信息 */ - const activeTextContent = imageStory.imageStory || ""; + /** 分析故事结果内容 */ + const [analyzedStoryContent, setAnalyzedStoryContent] = useState(""); /** 当前选中的分类 */ - const selectedCategory = imageStory.storyType || "auto"; + const [selectedCategory, setSelectedCategory] = useState("auto"); /** 故事类型选项 */ const storyTypeOptions = useMemo(() => imageStoryUseCase.getStoryTypeOptions(), [imageStoryUseCase]); @@ -69,6 +75,13 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { const updatedStory = imageStoryUseCase.getImageStory(); setImageStory(updatedStory); + // 更新活跃状态 + setActiveImageUrl(imageUrl); + setAnalyzedStoryContent(updatedStory.imageStory || ""); + setSelectedCategory(updatedStory.storyType || "auto"); + + + } catch (error) { console.error('图片上传分析失败:', error); throw error; @@ -87,7 +100,7 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { throw new Error('请先上传图片'); } - if (!activeTextContent) { + if (!analyzedStoryContent) { throw new Error('请先输入或生成故事内容'); } @@ -109,7 +122,7 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { } finally { setIsAnalyzing(false); } - }, [activeImageUrl, activeTextContent, imageStory]); + }, [activeImageUrl, analyzedStoryContent, imageStory]); /** * 更新故事类型 @@ -118,6 +131,7 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { const updateStoryType = useCallback((storyType: string): void => { imageStoryUseCase.updateStoryType(storyType); setImageStory(prev => ({ ...prev, storyType })); + setSelectedCategory(storyType); }, [imageStoryUseCase]); /** @@ -127,6 +141,9 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { const updateStoryContent = useCallback((content: string): void => { imageStoryUseCase.updateStoryContent(content); setImageStory(prev => ({ ...prev, imageStory: content })); + setAnalyzedStoryContent(content); + + }, [imageStoryUseCase]); /** @@ -139,19 +156,68 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { imageStory: "", storyType: "auto", }); + // 重置活跃状态 + setActiveImageUrl(""); + setAnalyzedStoryContent(""); + setSelectedCategory("auto"); setIsAnalyzing(false); setIsUploading(false); + + }, [imageStoryUseCase]); + /** + * 触发文件选择并自动分析 + */ + const triggerFileSelectionAndAnalyze = useCallback(async (): Promise => { + return new Promise((resolve, reject) => { + // 创建文件输入元素 + const fileInput = document.createElement("input"); + fileInput.type = "file"; + fileInput.accept = "image/*"; + fileInput.style.display = "none"; + + fileInput.onchange = async (e) => { + try { + const target = e.target as HTMLInputElement; + if (target.files && target.files[0]) { + // 使用传入的文件上传函数 + const uploadedImageUrl = await uploadFile(target.files[0], (progress) => { + console.log("上传进度:", progress); + }); + console.log('uploadedImageUrl', uploadedImageUrl) + // await uploadAndAnalyzeImage(uploadedImageUrl); + setActiveImageUrl(uploadedImageUrl); + } + resolve(); + } catch (error) { + reject(error); + } finally { + // 清理DOM + document.body.removeChild(fileInput); + } + }; + + fileInput.oncancel = () => { + document.body.removeChild(fileInput); + reject(); + }; + + document.body.appendChild(fileInput); + fileInput.click(); + }); + }, [uploadFile]); + return { imageStory, activeImageUrl, - activeTextContent, + analyzedStoryContent, selectedCategory, isAnalyzing, isUploading, storyTypeOptions, uploadAndAnalyzeImage, + triggerFileSelectionAndAnalyze, generateScript, updateStoryType, updateStoryContent, diff --git a/app/service/Interaction/templateStoryService.ts b/app/service/Interaction/templateStoryService.ts index fa22b75..97ba20c 100644 --- a/app/service/Interaction/templateStoryService.ts +++ b/app/service/Interaction/templateStoryService.ts @@ -1,4 +1,14 @@ -import { StoryTemplateEntity, RoleEntity } from "../domain/Entities"; +import { StoryTemplateEntity } from "../domain/Entities"; + +/** 模板角色接口 */ +interface TemplateRole { + /** 角色名 */ + role_name: string; + /** 照片URL */ + photo_url: string; + /** 声音URL */ + voice_url: string; +} import { TemplateStoryUseCase } from "../usecase/templateStoryUseCase"; import { getUploadToken, uploadToQiniu } from "@/api/common"; import { useState, useCallback, useMemo } from "react"; @@ -8,8 +18,10 @@ interface UseTemplateStoryService { templateStoryList: StoryTemplateEntity[]; /** 当前选中要使用的模板 */ selectedTemplate: StoryTemplateEntity | null; - /** 当前选中的活跃的角色 */ - activeRole: RoleEntity | null; + /** 当前选中的活跃角色索引 */ + activeRoleIndex: number; + /** 计算属性:当前活跃角色信息 */ + activeRole: TemplateRole | null; /** 加载状态 */ isLoading: boolean; /** 获取模板列表函数 */ @@ -18,19 +30,31 @@ interface UseTemplateStoryService { actionStory: () => Promise; /** 设置选中的模板 */ setSelectedTemplate: (template: StoryTemplateEntity | null) => void; - /** 设置活跃角色 */ - setActiveRole: (role: RoleEntity | null) => void; + /** 设置活跃角色索引 */ + setActiveRoleIndex: (index: number) => void; + /** 设置当前活跃角色的图片URL */ + setActiveRoleImage: (imageUrl: string) => void; + /** 设置当前活跃角色的音频URL */ + setActiveRoleAudio: (audioUrl: string) => void; } export const useTemplateStoryServiceHook = (): UseTemplateStoryService => { const [templateStoryList, setTemplateStoryList] = useState([]); const [selectedTemplate, setSelectedTemplate] = useState(null); - const [activeRole, setActiveRole] = useState(null); + const [activeRoleIndex, setActiveRoleIndex] = useState(0); const [isLoading, setIsLoading] = useState(false); /** 模板故事用例实例 */ const templateStoryUseCase = useMemo(() => new TemplateStoryUseCase(), []); + /** 计算属性:当前活跃角色信息 */ + const activeRole = useMemo(() => { + if (!selectedTemplate || activeRoleIndex < 0 || activeRoleIndex >= selectedTemplate.storyRole.length) { + return null; + } + return selectedTemplate.storyRole[activeRoleIndex]; + }, [selectedTemplate, activeRoleIndex]); + /** * 获取模板列表函数 */ @@ -68,15 +92,58 @@ export const useTemplateStoryServiceHook = (): UseTemplateStoryService => { } }, [selectedTemplate, templateStoryUseCase]); + /** + * 设置活跃角色索引 + */ + const handleSetActiveRoleIndex = useCallback((index: number): void => { + setActiveRoleIndex(index); + }, []); + + /** + * 设置当前活跃角色的图片URL + */ + const setActiveRoleImage = useCallback((imageUrl: string): void => { + if (!selectedTemplate || activeRoleIndex < 0 || activeRoleIndex >= selectedTemplate.storyRole.length) { + return; + } + + const updatedTemplate = { + ...selectedTemplate, + storyRole: selectedTemplate.storyRole.map((role, index) => + index === activeRoleIndex ? { ...role, photo_url: imageUrl } : role + ), + }; + setSelectedTemplate(updatedTemplate); + }, [selectedTemplate, activeRoleIndex]); + + /** + * 设置当前活跃角色的音频URL + */ + const setActiveRoleAudio = useCallback((audioUrl: string): void => { + if (!selectedTemplate || activeRoleIndex < 0 || activeRoleIndex >= selectedTemplate.storyRole.length) { + return; + } + + const updatedTemplate = { + ...selectedTemplate, + storyRole: selectedTemplate.storyRole.map((role, index) => + index === activeRoleIndex ? { ...role, voice_url: audioUrl } : role + ), + }; + setSelectedTemplate(updatedTemplate); + }, [selectedTemplate, activeRoleIndex]); return { templateStoryList, selectedTemplate, + activeRoleIndex, activeRole, isLoading, getTemplateStoryList, actionStory, setSelectedTemplate, - setActiveRole, + setActiveRoleIndex: handleSetActiveRoleIndex, + setActiveRoleImage, + setActiveRoleAudio, }; }; diff --git a/components/common/ChatInputBox.tsx b/components/common/ChatInputBox.tsx index 7298acc..87d9c95 100644 --- a/components/common/ChatInputBox.tsx +++ b/components/common/ChatInputBox.tsx @@ -1,6 +1,12 @@ "use client"; -import { useState, useRef } from "react"; +import { + useState, + useRef, + useEffect, + forwardRef, + useImperativeHandle, +} from "react"; import { ChevronDown, ChevronUp, @@ -20,15 +26,11 @@ import { } from "lucide-react"; import { Dropdown, Modal, Tooltip, Upload, Image } from "antd"; import { PlusOutlined, UploadOutlined, EyeOutlined } from "@ant-design/icons"; -import { - StoryTemplateEntity, -} from "@/app/service/domain/Entities"; -import { useTemplateStoryServiceHook } from "@/app/service/Interaction/templateStoryService"; +import { StoryTemplateEntity } from "@/app/service/domain/Entities"; import { useImageStoryServiceHook } from "@/app/service/Interaction/ImageStoryService"; -import { useUploadFile } from "@/app/service/domain/service"; import TemplateCard from "./templateCard"; import { AudioRecorder } from "./AudioRecorder"; -import { PhotoStoryMode } from "./PhotoStoryMode"; +import { useTemplateStoryServiceHook } from "@/app/service/Interaction/templateStoryService"; // 自定义音频播放器样式 const customAudioPlayerStyles = ` @@ -139,113 +141,35 @@ const RenderTemplateStoryMode = ({ isOpen: boolean; onClose: () => void; }) => { - // Mock数据 - 直接写在组件中用于渲染 - const mockTemplates = [ - { - id: "1", - name: "魔法森林冒险", - generateText: - "一个关于勇敢的小女孩在魔法森林中寻找失落宝藏的奇幻冒险故事。森林中充满了神秘的生物和隐藏的危险,她必须依靠智慧和勇气来克服重重困难。", - imageUrl: [ - "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=400&fit=crop", - ], - storyRole: [ - { - role_name: "艾莉娅", - photo_url: - "https://images.unsplash.com/photo-1494790108755-2616b612b786?w=200&h=200&fit=crop", - voice_url: "", - }, - { - role_name: "森林守护者", - photo_url: - "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=200&h=200&fit=crop", - voice_url: "", - }, - { - role_name: "魔法精灵", - photo_url: - "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=200&h=200&fit=crop", - voice_url: "", - }, - ], - }, - { - id: "2", - name: "太空探索之旅", - generateText: - "一支勇敢的宇航员团队在探索未知星球时发现了一个古老的文明遗迹。他们必须解开这个文明的秘密,同时面对来自宇宙深处的威胁。", - imageUrl: [ - "https://images.unsplash.com/photo-1446776811953-b23d0bd63bc8?w=400&h=400&fit=crop", - ], - storyRole: [ - { - role_name: "船长萨拉", - photo_url: - "https://images.unsplash.com/photo-1508214751196-bcfd4ca60f91?w=200&h=200&fit=crop", - voice_url: "", - }, - { - role_name: "科学家马克", - photo_url: - "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=200&h=200&fit=crop", - voice_url: "", - }, - { - role_name: "工程师安娜", - photo_url: - "https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=200&h=200&fit=crop", - voice_url: "", - }, - ], - }, - { - id: "3", - name: "古代王朝传奇", - generateText: - "在一个古老的东方王朝中,年轻的公主必须学会在复杂的宫廷政治中生存。她面临着背叛、阴谋和爱情的选择,最终成长为一位明智的统治者。", - imageUrl: [ - "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=400&fit=crop", - ], - storyRole: [ - { - role_name: "公主明月", - photo_url: - "https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=200&h=200&fit=crop", - voice_url: "", - }, - { - role_name: "大将军", - photo_url: - "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=200&h=200&fit=crop", - voice_url: "", - }, - { - role_name: "宫廷谋士", - photo_url: - "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=200&h=200&fit=crop", - voice_url: "", - }, - ], - }, - ]; + // 使用 hook 管理状态 + const { + templateStoryList, + selectedTemplate, + activeRoleIndex, + activeRole, + isLoading, + getTemplateStoryList, + actionStory, + setSelectedTemplate, + setActiveRoleIndex, + setActiveRoleImage, + setActiveRoleAudio, + } = useTemplateStoryServiceHook(); - // 本地状态管理 - const [templates] = useState(mockTemplates); - const [selectedTemplate, setSelectedTemplate] = useState(mockTemplates[0]); - const [loading] = useState(false); - const [selectedRoleIndex, setSelectedRoleIndex] = useState(0); + // 本地加载状态,用于 UI 反馈 const [localLoading, setLocalLoading] = useState(false); - // 角色资源状态管理 - 这些是UI交互必需的,无法简化 - /** 角色图片资源,key为角色索引,value为图片URL */ - const [roleImages, setRoleImages] = useState<{ [key: number]: string }>({}); - /** 角色音频资源,key为角色索引,value为音频URL */ - const [roleAudios, setRoleAudios] = useState<{ [key: number]: string }>({}); + // 组件挂载时获取模板列表 + useEffect(() => { + if (isOpen) { + getTemplateStoryList(); + } + }, [isOpen, getTemplateStoryList]); // 处理模板选择 const handleTemplateSelect = (template: StoryTemplateEntity) => { setSelectedTemplate(template); + setActiveRoleIndex(0); // 重置角色选择 }; // 处理确认操作 @@ -254,19 +178,15 @@ const RenderTemplateStoryMode = ({ try { setLocalLoading(true); - // Mock actionStory函数 - await new Promise((resolve) => setTimeout(resolve, 2000)); // 模拟2秒延迟 - const projectId = "mock-project-" + Date.now(); + const projectId = await actionStory(); console.log("Story action created:", projectId); onClose(); - setSelectedTemplate(mockTemplates[0]); // 重置为第一个模板 - // 清空角色资源状态 - setRoleImages({}); - setRoleAudios({}); - setSelectedRoleIndex(0); + // 重置状态 + setSelectedTemplate(null); + setActiveRoleIndex(0); } catch (error) { console.error("Failed to create story action:", error); - alert("Failed to create story action. Please try again."); + // 这里可以添加 toast 提示 } finally { setLocalLoading(false); } @@ -275,41 +195,16 @@ const RenderTemplateStoryMode = ({ // 处理角色图片上传 const handleRoleImageUpload = (roleIndex: number, file: any) => { if (file && selectedTemplate) { - // 这里可以添加实际的上传逻辑 - console.log("Character image uploaded:", file.name); // 模拟上传成功,设置图片URL const imageUrl = URL.createObjectURL(file); - setRoleImages((prev) => ({ ...prev, [roleIndex]: imageUrl })); - - // 直接更新模板中的角色图片 - const updatedTemplate = { - ...selectedTemplate, - storyRole: selectedTemplate.storyRole.map((role: any, index: number) => - index === roleIndex ? { ...role, photo_url: imageUrl } : role - ), - }; - - setSelectedTemplate(updatedTemplate); + setActiveRoleImage(imageUrl); } }; // 删除角色图片 const handleDeleteRoleImage = (roleIndex: number) => { - setRoleImages((prev) => { - const newState = { ...prev }; - delete newState[roleIndex]; - return newState; - }); - - // 同时从模板中删除图片 if (selectedTemplate) { - const updatedTemplate = { - ...selectedTemplate, - storyRole: selectedTemplate.storyRole.map((role: any, index: number) => - index === roleIndex ? { ...role, photo_url: "" } : role - ), - }; - setSelectedTemplate(updatedTemplate); + setActiveRoleImage(""); } }; // 模板列表渲染 @@ -318,7 +213,7 @@ const RenderTemplateStoryMode = ({

Story Templates

- {templates.map((template, index) => ( + {templateStoryList.map((template, index) => (
{ - return loading ? ( + return isLoading ? (
@@ -414,37 +309,29 @@ const RenderTemplateStoryMode = ({ onChange={(info) => { if (info.file.status === "done") { handleRoleImageUpload( - selectedRoleIndex, + activeRoleIndex, info.file.originFileObj ); } }} > - {roleImages[selectedRoleIndex] || - selectedTemplate.storyRole[selectedRoleIndex] - ?.photo_url ? ( + {activeRole?.photo_url ? (
Character Portrait - {roleImages[selectedRoleIndex] && ( - - )} +
@@ -468,52 +355,12 @@ const RenderTemplateStoryMode = ({ {/* 音频操作区域 - 使用新的 AudioRecorder 组件 */}
{ - // 保存到状态 - setRoleAudios((prev) => ({ - ...prev, - [selectedRoleIndex]: audioUrl, - })); - - // 保存到模板中 - if (selectedTemplate) { - const updatedTemplate = { - ...selectedTemplate, - storyRole: selectedTemplate.storyRole.map( - (role: any, index: number) => - index === selectedRoleIndex - ? { ...role, voice_url: audioUrl } - : role - ), - }; - setSelectedTemplate(updatedTemplate); - } + setActiveRoleAudio(audioUrl); }} onAudioDeleted={() => { - // 从状态中删除 - setRoleAudios((prev) => { - const newState = { ...prev }; - delete newState[selectedRoleIndex]; - return newState; - }); - - // 从模板中删除 - if (selectedTemplate) { - const updatedTemplate = { - ...selectedTemplate, - storyRole: selectedTemplate.storyRole.map( - (role: any, index: number) => - index === selectedRoleIndex - ? { ...role, voice_url: "" } - : role - ), - }; - setSelectedTemplate(updatedTemplate); - } + setActiveRoleAudio(""); }} />
@@ -530,11 +377,11 @@ const RenderTemplateStoryMode = ({ - {/* 图片故事模式UI */} - {isPhotoStoryMode && } + {/* 图片故事模式UI - 始终渲染,通过ref调用方法 */} +
{/* 可编辑的脚本输入区域 */}
- {isPhotoStoryMode && isAnalyzing ? ( -
- - Analyzing your image, please wait... -
- ) : isPhotoStoryMode && activeTextContent ? ( -
{activeTextContent}
- ) : ( - script - )} + {script}
{/* 占位符文本和获取创意按钮 */}
Describe the content you want to action. Get an @@ -1045,3 +837,130 @@ const ConfigOptions = ({
); }; + +/** + * 图片故事模式组件 + * 显示图片预览、分析状态和故事类型选择器 + * 使用自己的hook管理状态,通过ref暴露方法给父组件 + */ +const PhotoStoryMode = forwardRef< + { + enterPhotoStoryMode: () => void; + getStoryContent: () => string; + }, + { + onExitMode: () => void; + isVisible: boolean; + } +>(({ onExitMode, isVisible }, ref) => { + // 使用图片故事服务hook管理自己的状态 + const { + activeImageUrl, + selectedCategory, + isAnalyzing, + isUploading, + storyTypeOptions, + analyzedStoryContent, + updateStoryType, + resetImageStory, + updateStoryContent, + triggerFileSelectionAndAnalyze, + } = useImageStoryServiceHook(); + + // 通过ref暴露方法给父组件 + useImperativeHandle( + ref, + () => ({ + enterPhotoStoryMode: () => { + // 触发文件选择和分析 + triggerFileSelectionAndAnalyze().catch((error: unknown) => { + console.error("Failed to enter photo story mode:", error); + }); + }, + getStoryContent: () => analyzedStoryContent || "", + }), + [triggerFileSelectionAndAnalyze, analyzedStoryContent] + ); + + // 处理退出模式 + const handleExitMode = () => { + resetImageStory(); + onExitMode(); + }; + + // 如果不显示,返回null + if (!isVisible) { + return null; + } + + return ( +
+ {/* 左侧:图片预览区域和分析状态指示器 */} +
+ {/* 图片预览区域 - 使用Ant Design Image组件 */} + {activeImageUrl && ( +
+ Story inspiration, + maskClassName: + "flex items-center justify-center bg-black/50 hover:bg-black/70 transition-colors", + }} + /> +
+ )} + + {/* 删除图片按钮 - 简洁样式 */} + {activeImageUrl && ( + + )} + + {/* 分析状态指示器 */} + {isAnalyzing && ( +
+ + + {isUploading ? "Uploading image..." : "Analyzing image..."} + +
+ )} +
+ + {/* 右侧:故事类型选择器 */} + {activeImageUrl && ( + ({ + key: type.key, + label: ( +
+ {type.label} +
+ ), + })), + onClick: ({ key }) => updateStoryType(key), + }} + trigger={["click"]} + placement="bottomRight" + > + +
+ )} +
+ ); +}); diff --git a/components/common/PhotoStoryMode.tsx b/components/common/PhotoStoryMode.tsx deleted file mode 100644 index e8eea7e..0000000 --- a/components/common/PhotoStoryMode.tsx +++ /dev/null @@ -1,100 +0,0 @@ -"use client"; - -import { Loader2, Trash2, ChevronDown } from "lucide-react"; -import { Dropdown, Image } from "antd"; -import { EyeOutlined } from "@ant-design/icons"; -import { useImageStoryServiceHook } from "@/app/service/Interaction/ImageStoryService"; - -/** - * 图片故事模式组件 - * 显示图片预览、分析状态和故事类型选择器 - * 使用ImageStoryService hook管理状态和业务逻辑 - */ -export function PhotoStoryMode() { - // 使用图片故事服务hook - const { - activeImageUrl, - selectedCategory, - isAnalyzing, - isUploading, - storyTypeOptions, - updateStoryType, - resetImageStory, - } = useImageStoryServiceHook(); - - return ( -
- {/* 左侧:图片预览区域和分析状态指示器 */} -
- {/* 图片预览区域 - 使用Ant Design Image组件 */} -
- {activeImageUrl && ( - Story inspiration - ), - maskClassName: - "flex items-center justify-center bg-black/50 hover:bg-black/70 transition-colors", - }} - /> - )} -
- - {/* 删除图片按钮 - 简洁样式 */} - {activeImageUrl && ( - - )} - - {/* 分析状态指示器 */} - {isAnalyzing && ( -
- - - {isUploading - ? "Uploading image..." - : "Analyzing image..."} - -
- )} -
- - {/* 右侧:故事类型选择器 */} - {activeImageUrl && ( - ({ - key: type.key, - label: ( -
- {type.label} -
- ), - })), - onClick: ({ key }) => updateStoryType(key), - }} - trigger={["click"]} - placement="bottomRight" - > - -
- )} -
- ); -}