diff --git a/app/service/Interaction/ImageStoryService.ts b/app/service/Interaction/ImageStoryService.ts index 0b2148b..a78caad 100644 --- a/app/service/Interaction/ImageStoryService.ts +++ b/app/service/Interaction/ImageStoryService.ts @@ -23,6 +23,8 @@ interface UseImageStoryService { hasAnalyzed: boolean; /** 计算后的角色头像数据 */ avatarComputed: Array<{ name: string; url: string }>; + /** 原始用户描述 */ + originalUserDescription: string; /** 上传图片并分析 */ uploadAndAnalyzeImage: () => Promise; /** 触发文件选择 */ @@ -40,13 +42,14 @@ interface UseImageStoryService { /** 重置图片故事数据 */ resetImageStory: (showAnalysisState?: boolean) => void; setCharactersAnalysis: Dispatch> + setOriginalUserDescription: Dispatch> } export const useImageStoryServiceHook = (): UseImageStoryService => { // 基础状态 const [imageStory, setImageStory] = useState>({ imageUrl: "", - storyType: "auto", + storyType: "", }); // 图片相关状态 @@ -54,6 +57,8 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { // 故事内容状态(统一管理用户输入和AI分析结果) const [storyContent, setStoryContent] = useState(""); + // 原始用户描述 + const [originalUserDescription, setOriginalUserDescription] = useState(""); // 分析结果状态 /** 角色头像及名称 */ @@ -64,7 +69,7 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { const [potentialGenres, setPotentialGenres] = useState([]); // 分类状态 - const [selectedCategory, setSelectedCategory] = useState("Auto"); + const [selectedCategory, setSelectedCategory] = useState(""); // 流程状态 const [isLoading, setIsLoading] = useState(false); @@ -216,18 +221,17 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { const uploadAndAnalyzeImage = useCallback( async (): Promise => { try { - console.log('123123123', 123123123) setIsLoading(true); // 调用用例处理图片上传和分析 - await imageStoryUseCase.handleImageUpload(activeImageUrl); - + 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); @@ -235,18 +239,20 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { // 将AI分析的故事内容直接更新到统一的故事内容字段 updateStoryContent(updatedStory || ""); - setSelectedCategory("Auto"); + // 标记已分析 setHasAnalyzed(true); } catch (error) { console.error("图片上传分析失败:", error); + setHasAnalyzed(false); + throw error; } finally { setIsLoading(false); } }, - [activeImageUrl, imageStoryUseCase] + [activeImageUrl, imageStoryUseCase,storyContent,setOriginalUserDescription] ); /** @@ -314,8 +320,9 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { (oldName: string, newName: string) => { // 更新故事内容中的角色标签 setStoryContent((prev) => { - const regex = new RegExp(`${oldName}<\/role_name>`, "g"); - const content = prev.replace(regex, `${newName}`); + // 匹配新的角色标签格式 Dezhong Huang + const regex = new RegExp(`]*>${oldName}<\/role>`, "g"); + const content = prev.replace(regex, `${newName}`); imageStoryUseCase.updateStoryContent(content); return content; }); @@ -363,14 +370,15 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { // 重置所有状态 setImageStory({ imageUrl: "", - storyType: "auto", + storyType: "", }); setActiveImageUrl(""); updateStoryContent(""); setPotentialGenres([]); - setSelectedCategory("auto"); + setSelectedCategory(""); setHasAnalyzed(false); setIsLoading(false); + setOriginalUserDescription(""); }, [imageStoryUseCase]); /** @@ -437,6 +445,7 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { isLoading, hasAnalyzed, avatarComputed, + originalUserDescription, setCharactersAnalysis, uploadAndAnalyzeImage, triggerFileSelection, @@ -446,5 +455,6 @@ export const useImageStoryServiceHook = (): UseImageStoryService => { updateCharacterName, syncRoleNameToContent, resetImageStory, + setOriginalUserDescription }; }; diff --git a/app/service/domain/service.ts b/app/service/domain/service.ts index 647f1b6..c261025 100644 --- a/app/service/domain/service.ts +++ b/app/service/domain/service.ts @@ -1,6 +1,5 @@ - import { getUploadToken, uploadToQiniu } from "@/api/common"; -import { useState, useCallback } from "react"; +import { useState, useCallback, useEffect } from "react"; import { ScriptEditKey } from "../usecase/ScriptEditUseCase"; /** * 渲染数据转换器 @@ -13,7 +12,7 @@ export function parseScriptBlock( key: ScriptEditKey, headerName: string, scriptText: string, - contentType?: 'paragraph' | 'bold' | 'italic' | 'heading' | 'tag', + contentType?: "paragraph" | "bold" | "italic" | "heading" | "tag" ) { return { id: key, @@ -27,8 +26,6 @@ export function parseScriptBlock( }; } - - /** * 用于上传文件到七牛云的自定义 Hook * @returns {object} - 包含上传函数和加载状态 @@ -45,14 +42,17 @@ export function useUploadFile() { * @throws {Error} - 上传失败时抛出异常 */ const uploadFile = useCallback( - async (file: File, onProgress?: (progress: number) => void): Promise => { + async ( + file: File, + onProgress?: (progress: number) => void + ): Promise => { try { setIsUploading(true); const { token } = await getUploadToken(); const fileUrl = await uploadToQiniu(file, token, onProgress); return fileUrl; } catch (err) { - console.error('文件上传失败:', err); + console.error("文件上传失败:", err); throw err; } finally { setIsUploading(false); @@ -63,3 +63,65 @@ export function useUploadFile() { return { uploadFile, isUploading }; } + +/**加载文案定时变 */ +export function useLoadScriptText(loading: boolean): { loadingText: string } { + // 如果loading 为true 则每五秒切换一次文本,如果变false 则停止切换,且重置文本位置 + const tests = [ + "loading...", + "Brainstorming initial story concepts and themes.", + "Drafting the screenplay's first outline.", + "Refining character arcs and plot points.", + "Finalizing the script with dialogue polish.", + "Creating detailed storyboards for key scenes.", + "Scouting potential filming locations.", + "Designing mood boards for visual aesthetics.", + "Casting actors to bring characters to life.", + "Scheduling production timelines and shoots.", + "Securing permits for on-location filming.", + "Building sets to match the story’s vision.", + "Designing costumes for character authenticity.", + "Planning lighting setups for each scene.", + "Renting equipment for high-quality production.", + "Rehearsing actors for seamless performances.", + "Setting up cameras for the first shot.", + "Filming establishing shots for scene context.", + "Capturing key dialogue scenes with precision.", + "Recording action sequences with dynamic angles.", + "Filming close-ups to capture emotions.", + "Wrapping principal photography on set.", + "Reviewing dailies for quality assurance.", + "Organizing raw footage for editing.", + "Editing scenes for narrative flow.", + "Adding sound effects to enhance immersion.", + "Composing the film’s musical score.", + "Mixing audio for balanced sound design.", + "Applying color grading for visual consistency.", + "Rendering visual effects for final polish.", + "Exporting the final cut for distribution.", + ]; + const [currentIndex, setCurrentIndex] = useState(0); + const [intervalId, setIntervalId] = useState(null); + useEffect(() => { + if (loading) { + const interval = setInterval(() => { + setCurrentIndex((prev) => (prev + 1) % tests.length); + }, 5000); + setIntervalId(interval); + } else { + if (intervalId) { + clearInterval(intervalId); + setIntervalId(null); + setCurrentIndex(0); + } + } + return () => { + if (intervalId) { + clearInterval(intervalId); + setIntervalId(null); + setCurrentIndex(0); + } + }; + }, [loading, tests.length]); + return { loadingText: tests[currentIndex] }; +} diff --git a/app/service/usecase/imageStoryUseCase.ts b/app/service/usecase/imageStoryUseCase.ts index 8216d23..e9d1bde 100644 --- a/app/service/usecase/imageStoryUseCase.ts +++ b/app/service/usecase/imageStoryUseCase.ts @@ -8,10 +8,13 @@ import { MovieStartDTO, CharacterAnalysis } from "@/api/DTO/movie_start_dto"; */ export class ImageStoryUseCase { /** 当前图片故事数据 */ - imageStory: Partial = { + imageStory: ImageStoryEntity = { + id: "", + imageAnalysis: "", + roleImage: [], imageUrl: "", imageStory: "", - storyType: "Auto", + storyType: "", }; /** 故事梗概 */ @@ -28,7 +31,6 @@ export class ImageStoryUseCase { /** 是否正在上传 */ isUploading: boolean = false; - constructor() {} @@ -46,9 +48,12 @@ export class ImageStoryUseCase { */ resetImageStory(): void { this.imageStory = { + id: "", + imageAnalysis: "", + roleImage: [], imageUrl: "", imageStory: "", - storyType: "Auto", + storyType: "", }; this.storyLogline = ""; this.charactersAnalysis = []; @@ -62,7 +67,7 @@ export class ImageStoryUseCase { * @param {string} imageUrl - 已上传的图片URL * @returns {Promise} */ - async handleImageUpload(imageUrl: string): Promise { + async handleImageUpload(imageUrl: string) { try { this.isUploading = false; // 图片已上传,设置上传状态为false this.isAnalyzing = true; @@ -71,7 +76,7 @@ export class ImageStoryUseCase { this.setImageStory({ imageUrl }); // 调用AI分析接口 - await this.analyzeImageWithAI(); + return await this.analyzeImageWithAI(); } catch (error) { console.error("图片分析失败:", error); @@ -87,7 +92,7 @@ export class ImageStoryUseCase { * 使用AI分析图片 * @returns {Promise} */ - async analyzeImageWithAI(): Promise { + async analyzeImageWithAI() { console.log('this.imageStory.imageUrl', this.imageStory.imageUrl) try { // 调用AI分析接口 @@ -102,6 +107,7 @@ export class ImageStoryUseCase { // 组合成ImageStoryEntity this.composeImageStoryEntity(response.data); + return this.imageStory; } else { throw new Error("AI分析失败"); } @@ -140,13 +146,15 @@ export class ImageStoryUseCase { y: character.region?.y || 0, width: character.region?.width || 0, height: character.region?.height || 0, - } + }, + })) || []; // 更新ImageStoryEntity this.setImageStory({ + ...this.imageStory, imageAnalysis: data.story_logline || "", - storyType: "Auto", // 使用第一个分类作为故事类型 + storyType: data.potential_genres[0] || "", // 使用第一个分类作为故事类型 roleImage, }); } diff --git a/components/common/ChatInputBox.tsx b/components/common/ChatInputBox.tsx index bc9c9db..fe166ca 100644 --- a/components/common/ChatInputBox.tsx +++ b/components/common/ChatInputBox.tsx @@ -35,6 +35,7 @@ import StarterKit from "@tiptap/starter-kit"; import { HighlightTextExtension } from "@/components/ui/main-editor/HighlightText"; import Placeholder from "@tiptap/extension-placeholder"; import { createMovieProjectV1 } from "@/api/video_flow"; +import { useLoadScriptText } from "@/app/service/domain/service"; // 自定义音频播放器样式 const customAudioPlayerStyles = ` @@ -914,10 +915,11 @@ const RoleHighlightEditor = ({ to < doc.content.size ? doc.textBetween(to, Math.min(doc.content.size, to + 50)) : ""; - // TODO role id 的结构 - const beforeMatch = textBefore.match(/[^<]*$/); - const afterMatch = textAfter.match(/^[^>]*<\/role_name>/); + // 匹配新的角色标签格式 Dezhong Huang + const beforeMatch = textBefore.match(/]*>[^<]*$/); + const afterMatch = textAfter.match(/^[^>]*<\/role>/); + // 如果光标在角色标签内,阻止输入(只允许删除操作) if (beforeMatch || afterMatch) { if (event.key !== "Backspace" && event.key !== "Delete") { event.preventDefault(); @@ -938,9 +940,9 @@ const RoleHighlightEditor = ({ return; } - // 将带标签的内容转换为高亮显示 + // 将带标签的内容转换为高亮显示(支持新的角色标签格式) const htmlContent = content.replace( - /([^<]+)<\/role_name>/g, + /]*>([^<]+)<\/role>/g, '$1' ); editor.commands.setContent(htmlContent, { emitUpdate: false }); @@ -1028,8 +1030,9 @@ const PhotoStoryModal = ({ avatarComputed, uploadAndAnalyzeImage, setCharactersAnalysis, + originalUserDescription } = useImageStoryServiceHook(); - + const { loadingText } = useLoadScriptText(isLoading); // 重置状态 const handleClose = () => { resetImageStory(); @@ -1068,7 +1071,7 @@ const PhotoStoryModal = ({ } > - +
{/* 弹窗头部 */}
@@ -1083,7 +1086,7 @@ const PhotoStoryModal = ({
]*>${avatar.name}<\/role>`, "g"), + "" + ) .replace( new RegExp(`\\b${avatar.name}\\b`, "g"), "" @@ -1164,7 +1171,6 @@ const PhotoStoryModal = ({ .trim(); // 更新状态 updateStoryContent(updatedStory); - // 注意:这里需要直接更新 charactersAnalysis,但 hook 中没有提供 setter }} className="absolute top-1 right-1 w-4 h-4 bg-black/[0.05] border border-black/[0.1] text-white rounded-full flex items-center justify-center transition-colors opacity-0 group-hover:opacity-100 z-10" > @@ -1196,7 +1202,7 @@ const PhotoStoryModal = ({ {hasAnalyzed && potentialGenres.length > 0 && (
- {["Auto", ...potentialGenres].map((genre) => ( + {[ ...potentialGenres].map((genre) => (
)}
+ {/* 原始用户描述的展示 */} + {originalUserDescription && ( +
Your Provided Text:{originalUserDescription}
+ )} +
{/* 文本输入框 */}