import { ChatMessage, MessageBlock, FetchMessagesRequest, SendMessageRequest, ChatConfig, ApiResponse, RealApiMessage, ApiMessageContent, MessagesResponse, FunctionName, ProjectInit, ScriptSummary, CharacterGeneration, SketchGeneration, ShotSketchGeneration, ShotVideoGeneration } from "./types"; import { post } from "@/api/request"; // 空消息 默认展示 const EMPTY_MESSAGES: RealApiMessage[] = [ { id: 1, role: 'assistant', content: JSON.stringify([{ type: 'text', content: '🌟Welcome to MovieFlow 🎬✨\nTell me your idea~💡\nI am your AI assistant🤖, I can help you:\n🎭 Generate actor images\n📽️ Generate scene & shot sketches\n🎞️ Complete video creation\n\nLet\'s start our creative journey together!❤️' }]), created_at: new Date().toISOString(), function_name: undefined, custom_data: undefined, status: 'success', intent_type: 'function_call' } ]; // 用户积分不足消息 const NoEnoughCreditsMessageBlocks: MessageBlock[] = [ { type: 'text', text: 'Insufficient credits.' }, { type: 'link', text: 'Upgrade to continue.', url: '/pricing' } ]; /** * 类型守卫函数 */ function isProjectInit(data: any): data is ProjectInit { return data && 'project_data' in data; } function isScriptSummary(data: any): data is ScriptSummary { return data && 'summary' in data; } function isCharacterGeneration(data: any): data is CharacterGeneration { return data && 'character_name' in data && 'image_path' in data && 'completed_count' in data && 'total_count' in data; } function isSketchGeneration(data: any): data is SketchGeneration { return data && 'sketch_name' in data && 'image_path' in data && 'completed_count' in data && 'total_count' in data; } function isShotSketchGeneration(data: any): data is ShotSketchGeneration { return data && 'shot_type' in data && 'atmosphere' in data && 'key_action' in data && 'url' in data && 'completed_count' in data && 'total_count' in data; } function isShotVideoGeneration(data: any): data is ShotVideoGeneration { return data && 'prompt_json' in data && 'urls' in data && 'completed_count' in data && 'total_count' in data; } function displayText(title: string, content: string, isLast: boolean = false) { return `${content ? `${title}: ${content}${isLast ? '' : '\n'}` : ''}` } /** * 系统消息转换为blocks数组 */ function transformSystemMessage( functionName: FunctionName, content: string, customData: ProjectInit | ScriptSummary | CharacterGeneration | SketchGeneration | ShotSketchGeneration | ShotVideoGeneration ): MessageBlock[] { let blocks: MessageBlock[] = []; switch (functionName) { case 'create_project': if (isProjectInit(customData)) { blocks = [{ type: 'text', text: `🎬 According to your input "${customData.project_data.script}", I have completed the initialization of the project.\n${content}` }]; } break; case 'generate_script_summary': if (isScriptSummary(customData)) { blocks = [ { type: 'text', text: `🎬 I have completed the script summary generation.\n\n${customData.summary}\n\n${content}` } ]; } break; case 'generate_character': if (isCharacterGeneration(customData)) { blocks = [{ type: 'text', text: `🎭 Actor "${customData.character_name}" is ready.` }, { type: 'image', url: customData.image_path }, { type: 'text', text: 'The actor image is for reference only, and can be adjusted after the video is generated.' }, { type: 'progress', value: customData.completed_count, total: customData.total_count, label: `Completed ${customData.completed_count} actors, total ${customData.total_count} actors` }, { type: 'text', text: `\n${content}` }]; } break; case 'generate_sketch': if (isSketchGeneration(customData)) { blocks = [{ type: 'text', text: `🎨 Scene "${customData.sketch_name}" reference image generated \n` }, { type: 'image', url: customData.image_path }, { type: 'text', text: 'The scene image is for reference only, and can be adjusted after the video is generated.' }, { type: 'progress', value: customData.completed_count, total: customData.total_count, label: `Completed ${customData.completed_count} scenes, total ${customData.total_count} scenes` }, { type: 'text', text: `\n${content}` }]; } break; case 'generate_shot_sketch': if (isShotSketchGeneration(customData)) { blocks = [{ type: 'text', text: `🎬 Storyboard static frame generation \n${displayText('Shot type', customData.shot_type)}${displayText('Atmosphere', customData.atmosphere)}${displayText('Key action', customData.key_action, true)}` }, { type: 'image', url: customData.url }, { type: 'text', text: 'The storyboard static frame image is for reference only, and can be adjusted after the video is generated.' }, { type: 'progress', value: customData.completed_count, total: customData.total_count, label: `Completed ${customData.completed_count} storyboard static frames, total ${customData.total_count} storyboard static frames` }, { type: 'text', text: `\n${content}` }]; } break; case 'generate_video': if (isShotVideoGeneration(customData)) { blocks.push({ type: 'text', text: `🎬 There are ${customData.urls.length} videos in this shot. \nCore atmosphere: ${customData.prompt_json.core_atmosphere}` }); customData.urls.forEach((url: string) => { blocks.push({ type: 'video', url: url }); }); blocks.push({ type: 'text', text: 'You can edit the video on the editing line later.' }, { type: 'progress', value: customData.completed_count, total: customData.total_count, label: `Completed ${customData.completed_count} shots, total ${customData.total_count} shots` }, { type: 'text', text: `\n${content}` }) } break; } return blocks; } /** * 将API响应转换为ChatMessage格式 */ function transformMessage(apiMessage: RealApiMessage): ChatMessage { try { const { id, role, content, created_at, function_name, custom_data, status, intent_type } = apiMessage; let message: ChatMessage = { id: id ? id.toString() : Date.now().toString(), role: role, createdAt: new Date(created_at).getTime(), blocks: [], chatType: intent_type, status: status || 'success', }; const errorMessage = custom_data?.error_message; if (errorMessage && errorMessage === 'no enough credits') { message.blocks = NoEnoughCreditsMessageBlocks; } else { if (role === 'assistant' || role === 'user') { try { const contentObj = JSON.parse(content); const contentArray = Array.isArray(contentObj) ? contentObj : [contentObj]; contentArray.forEach((c: ApiMessageContent) => { if (c.type === "text") { message.blocks.push({ type: "text", text: c.content }); } else if (c.type === "image") { message.blocks.push({ type: "image", url: c.content }); } else if (c.type === "video") { message.blocks.push({ type: "video", url: c.content }); } else if (c.type === "audio") { message.blocks.push({ type: "audio", url: c.content }); } else if (c.type === "link") { message.blocks.push({ type: "link", text: c.content, url: c.url || '' }); } }); // todo: 需要确认是否需要添加applyButton // if (role === 'assistant' && function_name === 'modify_video_with_runway') { // message.blocks.push({ type: "applyButton", text: "Apply" }); // } } catch (error) { // 如果 JSON 解析失败,将整个 content 作为文本内容 message.blocks.push({ type: "text", text: content }); } } else if (role === 'system' && function_name && custom_data) { // 处理系统消息 message.blocks = transformSystemMessage(function_name, content, custom_data); } else { message.blocks.push({ type: "text", text: content }); } } // 如果没有有效的 blocks,至少添加一个文本块 if (message.blocks.length === 0) { message.blocks.push({ type: "text", text: "No content" }); } return message; } catch (error) { console.error("Failed to transform message format:", error, apiMessage); // 返回一个带有错误信息的消息 return { id: new Date().getTime().toString(), role: apiMessage.role, createdAt: new Date(apiMessage.created_at).getTime(), blocks: [{ type: "text", text: "Message format error" }], chatType: 'chat', status: 'error', }; } } /** * 获取消息列表 */ export async function fetchMessages( config: ChatConfig, offset: number = 0, limit: number = 50 ): Promise<{ messages: ChatMessage[], hasMore: boolean, totalCount: number, }> { const request: FetchMessagesRequest = { session_id: `project_${config.projectId}_user_${config.userId}`, limit, offset, }; try { console.log('Send history message request:', request); const response = await post>("/intelligent/history", request); console.log('Receive history message response:', response); // 确保 response.data 和 messages 存在 if (!response.data || !response.data.messages) { console.error('History message response format error:', response); return { messages: [], hasMore: false, totalCount: 0 }; } // 转换消息并按时间排序 if (response.data.messages.length === 0) { return { messages: EMPTY_MESSAGES.map(transformMessage), hasMore: false, totalCount: 0 }; } return { messages: response.data.messages .map(transformMessage) .sort((a, b) => Number(a.id) - Number(b.id)), hasMore: response.data.has_more, totalCount: response.data.total_count }; } catch (error) { console.error("Failed to get message history:", error); throw error; } } /** * 发送新消息 */ export async function sendMessage( blocks: MessageBlock[], config: ChatConfig, videoId?: string ): Promise { // 提取文本、图片和视频 const textBlocks = blocks.filter(b => b.type === "text"); const imageBlocks = blocks.filter(b => b.type === "image"); const videoBlocks = blocks.filter(b => b.type === "video"); const request: SendMessageRequest = { session_id: `project_${config.projectId}_user_${config.userId}`, user_input: textBlocks.map(b => (b as { text: string }).text).join("\n"), project_id: config.projectId, user_id: config.userId.toString(), }; // 如果有图片,添加第一张图片的URL if (imageBlocks.length > 0) { request.image_url = (imageBlocks[0] as { url: string }).url; } // 如果有视频,添加视频URL if (videoBlocks.length > 0) { request.video_url = (videoBlocks[0] as { url: string }).url; } // 如果有视频ID,添加到请求中 if (videoId) { request.video_id = videoId; } try { console.log('Send message request:', request); await post>("/intelligent/chat", request); } catch (error) { console.error("Send message failed:", error); throw error; } } /** * 重试发送消息 */ export async function retryMessage( messageId: string, config: ChatConfig ): Promise { // TODO: 实现实际的重试逻辑,可能需要保存原始消息内容 // 这里简单重用发送消息的接口 return sendMessage([{ type: "text", text: "Retry message" }], config); }