diff --git a/app/service/Interaction/ImageStoryService.ts b/app/service/Interaction/ImageStoryService.ts index a78caad..feefc7d 100644 --- a/app/service/Interaction/ImageStoryService.ts +++ b/app/service/Interaction/ImageStoryService.ts @@ -1,7 +1,13 @@ import { ImageStoryEntity } from "../domain/Entities"; import { useUploadFile } from "../domain/service"; import { ImageStoryUseCase } from "../usecase/imageStoryUseCase"; -import { useState, useCallback, useMemo, Dispatch, SetStateAction } from "react"; +import { + useState, + useCallback, + useMemo, + Dispatch, + SetStateAction, +} from "react"; import { CharacterAnalysis } from "@/api/DTO/movie_start_dto"; interface UseImageStoryService { @@ -41,8 +47,12 @@ interface UseImageStoryService { syncRoleNameToContent: (oldName: string, newName: string) => void; /** 重置图片故事数据 */ resetImageStory: (showAnalysisState?: boolean) => void; - setCharactersAnalysis: Dispatch> - setOriginalUserDescription: Dispatch> + /** 生成动作电影 */ + actionMovie: () => Promise; + /** 设置角色分析 */ + setCharactersAnalysis: Dispatch>; + /** 设置原始用户描述 */ + setOriginalUserDescription: Dispatch>; } export const useImageStoryServiceHook = (): UseImageStoryService => { @@ -58,7 +68,8 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { // 故事内容状态(统一管理用户输入和AI分析结果) const [storyContent, setStoryContent] = useState(""); // 原始用户描述 - const [originalUserDescription, setOriginalUserDescription] = useState(""); + const [originalUserDescription, setOriginalUserDescription] = + useState(""); // 分析结果状态 /** 角色头像及名称 */ @@ -88,7 +99,11 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { */ const generateAvatarFromRegion = useCallback( (character: CharacterAnalysis, imageUrl: string) => { - if (!character.region || !character.region.width || !character.region.height) { + if ( + !character.region || + !character.region.width || + !character.region.height + ) { return; } // 创建图片对象 @@ -218,42 +233,45 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { * 上传图片并分析 * @param {string} imageUrl - 已上传的图片URL */ - const uploadAndAnalyzeImage = useCallback( - async (): Promise => { - try { - setIsLoading(true); + const uploadAndAnalyzeImage = useCallback(async (): Promise => { + try { + setIsLoading(true); - // 调用用例处理图片上传和分析 - const newImageStory = await imageStoryUseCase.handleImageUpload(activeImageUrl); - setOriginalUserDescription(storyContent) - // 获取更新后的数据 - const updatedStory = imageStoryUseCase.storyLogline; - const updatedCharacters = imageStoryUseCase.charactersAnalysis; - const updatedGenres = imageStoryUseCase.potentialGenres; - const updatedImageStory = imageStoryUseCase.imageStory; - setSelectedCategory(imageStoryUseCase.potentialGenres[0]); - // 更新所有响应式状态 - setCharactersAnalysis(updatedCharacters); - setPotentialGenres(updatedGenres); - setImageStory(updatedImageStory); + // 调用用例处理图片上传和分析 + const newImageStory = await imageStoryUseCase.handleImageUpload( + activeImageUrl + ); + setOriginalUserDescription(storyContent); + // 获取更新后的数据 + const updatedStory = imageStoryUseCase.storyLogline; + const updatedCharacters = imageStoryUseCase.charactersAnalysis; + const updatedGenres = imageStoryUseCase.potentialGenres; + const updatedImageStory = imageStoryUseCase.imageStory; + setSelectedCategory(imageStoryUseCase.potentialGenres[0]); + // 更新所有响应式状态 + setCharactersAnalysis(updatedCharacters); + setPotentialGenres(updatedGenres); + setImageStory(updatedImageStory); - // 将AI分析的故事内容直接更新到统一的故事内容字段 - updateStoryContent(updatedStory || ""); + // 将AI分析的故事内容直接更新到统一的故事内容字段 + updateStoryContent(updatedStory || ""); + // 标记已分析 + setHasAnalyzed(true); + } catch (error) { + console.error("图片上传分析失败:", error); + setHasAnalyzed(false); - // 标记已分析 - setHasAnalyzed(true); - } catch (error) { - console.error("图片上传分析失败:", error); - setHasAnalyzed(false); - - throw error; - } finally { - setIsLoading(false); - } - }, - [activeImageUrl, imageStoryUseCase,storyContent,setOriginalUserDescription] - ); + throw error; + } finally { + setIsLoading(false); + } + }, [ + activeImageUrl, + imageStoryUseCase, + storyContent, + setOriginalUserDescription, + ]); /** * 触发生成剧本函数 @@ -306,10 +324,13 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { * 更新故事内容 * @param {string} content - 新的故事内容 */ - const updateStoryContent = useCallback((content: string): void => { - setStoryContent(content); - imageStoryUseCase.updateStoryContent(content); - }, [imageStoryUseCase]); + const updateStoryContent = useCallback( + (content: string): void => { + setStoryContent(content); + imageStoryUseCase.updateStoryContent(content); + }, + [imageStoryUseCase] + ); /** * 同步角色名称到故事内容 @@ -326,7 +347,6 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { imageStoryUseCase.updateStoryContent(content); return content; }); - }, [imageStoryUseCase] ); @@ -384,57 +404,67 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { /** * 触发文件选择并自动分析 */ - const triggerFileSelection = - useCallback(async (): Promise => { - return new Promise((resolve, reject) => { - // 创建文件输入元素 - const fileInput = document.createElement("input"); - fileInput.type = "file"; - fileInput.accept = "image/*"; - fileInput.style.display = "none"; + const triggerFileSelection = 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]) { - setIsLoading(true); + fileInput.onchange = async (e) => { + try { + const target = e.target as HTMLInputElement; + if (target.files && target.files[0]) { + setIsLoading(true); - // 使用传入的文件上传函数 - const uploadedImageUrl = await uploadFile( - target.files[0], - (progress) => { - console.log("上传进度:", progress); - } - ); + // 使用传入的文件上传函数 + const uploadedImageUrl = await uploadFile( + target.files[0], + (progress) => { + console.log("上传进度:", progress); + } + ); - // 设置图片URL - setActiveImageUrl(uploadedImageUrl); - setImageStory((prev) => ({ - ...prev, - imageUrl: uploadedImageUrl, - })); - - } - resolve(); - } catch (error) { - reject(error); - } finally { - setIsLoading(false); - // 清理DOM - document.body.removeChild(fileInput); + // 设置图片URL + setActiveImageUrl(uploadedImageUrl); + setImageStory((prev) => ({ + ...prev, + imageUrl: uploadedImageUrl, + })); } - }; - - fileInput.oncancel = () => { + resolve(); + } catch (error) { + reject(error); + } finally { + setIsLoading(false); + // 清理DOM document.body.removeChild(fileInput); - reject(); + } + }; + + fileInput.oncancel = () => { + document.body.removeChild(fileInput); + reject(); + }; + + document.body.appendChild(fileInput); + fileInput.click(); + }); + }, [uploadFile]); + + const actionMovie = useCallback(async (): Promise => { + try { + if (hasAnalyzed) { + const params = { + content: storyContent, + category: selectedCategory, }; - - document.body.appendChild(fileInput); - fileInput.click(); - }); - }, [uploadFile]); - + } + } catch (error) { + console.error("图片上传分析失败:", error); + } + }, [activeImageUrl, imageStoryUseCase]); return { imageStory, activeImageUrl, @@ -455,6 +485,7 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { updateCharacterName, syncRoleNameToContent, resetImageStory, - setOriginalUserDescription + setOriginalUserDescription, + actionMovie, }; }; diff --git a/app/service/Interaction/ShotService.ts b/app/service/Interaction/ShotService.ts index 08010e4..44a71fa 100644 --- a/app/service/Interaction/ShotService.ts +++ b/app/service/Interaction/ShotService.ts @@ -209,7 +209,6 @@ export const useShotService = (): UseShotService => { ) ); } - // 返回当前选中的片段,因为现在API返回的是任务状态而不是完整的片段 return selectedSegment!; } catch (error) { diff --git a/app/service/uml/DDDLayer.md b/app/service/uml/DDDLayer.md new file mode 100644 index 0000000..25dc8ef --- /dev/null +++ b/app/service/uml/DDDLayer.md @@ -0,0 +1,150 @@ + +## 🎯 各层职责与内容 + +### 1. Component (UI层) +**职责**:纯展示逻辑,用户交互处理 +**内容**: +- React组件(Modal、Form、Button等) +- 样式和布局逻辑 +- 用户交互事件绑定 +- 通过props接收数据和回调函数 + +**设计原则**: +- 不包含业务逻辑 +- 不直接操作状态 +- 通过回调函数与上层通信 +- 可复用和可测试 + +### 2. Hook (状态管理层) +**职责**:状态管理、副作用处理、业务逻辑组合 +**内容**: +- 状态管理Hook(useState、useReducer等) +- 副作用处理(useEffect、useCallback等) +- 业务逻辑组合(调用多个UseCase) +- 提供统一的数据接口给组件 + +**设计原则**: +- 协调全局状态和本地状态 +- 处理异步操作和副作用 +- 组合多个UseCase的调用 +- 提供响应式的数据接口 + +### 3. UseCase (业务逻辑层) +**职责**:核心业务规则、业务流程编排、领域逻辑验证 +**内容**: +- 业务用例类(ImageStoryUseCase、ScriptGenerationUseCase等) +- 完整的业务流程方法 +- 业务规则验证逻辑 +- 业务实体构建和管理 + +**设计原则**: +- 包含完整的业务流程 +- 执行业务规则验证 +- 协调多个Service的调用 +- 返回领域实体,而不是原始数据 +- 无状态,方法调用间不依赖实例状态 + +### 4. Service (外部服务层) +**职责**:外部服务集成、技术实现细节、基础设施 +**内容**: +- 外部API调用服务(ImageProcessingService、AIAnalysisService等) +- 技术实现细节(图片处理、网络请求等) +- 错误处理和重试逻辑 +- 可以被多个UseCase复用的服务 + +**设计原则**: +- 专注于技术实现细节 +- 处理外部API调用 +- 提供统一的错误处理 +- 实现接口隔离原则 + +### 5. Repository (数据访问层) +**职责**:数据持久化、数据查询、数据转换 +**内容**: +- 数据访问类(ImageStoryRepository、CharacterRepository等) +- 数据持久化逻辑 +- 数据查询和过滤 +- 数据转换(Entity ↔ Data) + +**设计原则**: +- 封装数据访问细节 +- 提供统一的CRUD接口 +- 处理数据转换 +- 实现数据访问的抽象 + +## 📊 状态管理策略 + +**状态分类与存储位置**: +- **业务核心状态** → Zustand Store(currentStory、storyList等) +- **UI交互状态** → 组件useState(isModalOpen、localInput等) +- **业务流程状态** → UseCase私有状态(analysisProgress等) +- **用户偏好状态** → Zustand Store(selectedCategory、theme等) +- **临时计算状态** → useMemo/useCallback(filteredStories等) + +## 数据流向 + +**状态流转过程**: +用户操作 → 组件本地状态 → Hook协调 → UseCase业务逻辑 → 全局状态更新 → 组件重新渲染 +**具体流程**: +1. 用户在组件中触发操作 +2. 组件更新本地状态 +3. Hook协调多个UseCase调用 +4. UseCase执行业务逻辑和规则验证 +5. 更新全局状态 +6. 组件重新渲染显示结果 + +## 设计模式应用 + +**核心模式**: +1. **依赖注入**:UseCase通过构造函数注入Service依赖 +2. **工厂模式**:创建UseCase实例,管理依赖关系 +3. **策略模式**:可插拔的业务策略(如不同的AI分析策略) +4. **观察者模式**:状态变化通知,组件响应式更新 + +## 🧪 测试策略 + +**分层测试**: +- **Component层**:组件渲染和交互测试 +- **Hook层**:状态管理和副作用测试 +- **UseCase层**:业务逻辑和规则验证测试 +- **Service层**:外部服务集成测试 +- **Repository层**:数据访问逻辑测试 + +## 错误处理机制 + +**错误分类**: +- **领域错误**:业务规则验证失败(StoryValidationError) +- **基础设施错误**:外部服务调用失败(ImageUploadError) +- **应用错误**:应用逻辑错误(UseCaseError) + +**错误处理流程**: +1. Service层捕获原始错误并转换为应用错误 +2. UseCase层抛出领域错误 +3. Hook层统一捕获和处理错误 +4. 组件层显示错误信息 + +## 性能优化策略 + +**状态优化**: +- 使用useMemo优化计算属性 +- 使用useCallback优化函数引用 +- 合理使用React.memo包装组件 + +**业务逻辑优化**: +- UseCase方法设计为无状态,避免实例状态维护 +- Service层实现缓存和重试机制 +- Repository层实现数据分页和懒加载 + +## 📋 开发检查清单 + +**新增功能时**: +- [ ] 是否在正确的层次添加代码? +- [ ] 是否遵循单一职责原则? +- [ ] 是否定义了清晰的接口? +- [ ] 是否处理了错误情况? +- [ ] 是否添加了相应的测试? + +**重构代码时**: +- [ ] 是否保持了接口的向后兼容? +- [ ] 是否更新了相关的测试? +- [ ] 是否验证了功能完整性? diff --git a/components/common/ChatInputBox.tsx b/components/common/ChatInputBox.tsx index fe166ca..9178556 100644 --- a/components/common/ChatInputBox.tsx +++ b/components/common/ChatInputBox.tsx @@ -702,10 +702,6 @@ export function ChatInputBox() { setIsPhotoStoryModalOpen(false)} - onConfirm={(storyContent, category) => { - setScript(storyContent); - setIsPhotoStoryModalOpen(false); - }} /> @@ -852,7 +848,7 @@ const ConfigOptions = ({ onClick: ({ key }) => onConfigChange(item.key, key), }} trigger={["click"]} - placement="bottomLeft" + placement="topRight" >