2025-08-24 22:09:26 +08:00

554 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
ChatMessage,
MessageBlock,
FetchMessagesRequest,
SendMessageRequest,
ChatConfig,
ApiResponse,
RealApiMessage,
ApiMessageContent,
MessagesResponse,
FunctionName,
ProjectInit,
ScriptSummary,
CharacterGeneration,
SketchGeneration,
ShotSketchGeneration,
ShotVideoGeneration
} from "./types";
import { post } from "@/api/request";
// Mock 数据
const MOCK_MESSAGES: RealApiMessage[] = [
// 用户发送剧本
{
id: 1,
role: 'user',
content: JSON.stringify([{
type: 'text',
content: '我想拍一个关于一个小女孩和她的机器人朋友的故事,故事发生在未来世界。'
}]),
created_at: '2024-03-20T10:00:00Z',
function_name: undefined,
custom_data: undefined,
status: 'success',
intent_type: 'chat'
},
// 项目初始化
{
id: 2,
role: 'system',
content: '我会帮您创建一个温馨感人的科幻短片,讲述人工智能与人类情感的故事。',
created_at: '2024-03-20T10:00:10Z',
function_name: 'create_project',
custom_data: {
project_data: {
script: '小女孩和机器人朋友的故事'
}
},
status: 'success',
intent_type: 'procedure'
},
// 剧本总结
{
id: 3,
role: 'system',
content: '故事概要在2045年的未来城市10岁的小女孩艾米丽收到了一个特别的生日礼物——一个具有高度情感智能的机器人伙伴"小星"。随着时间推移,他们建立了深厚的友谊。当小星因能源耗尽即将永久关闭时,艾米丽想尽办法寻找解决方案,最终通过她的坚持和创意,成功为小星找到了新的能源,让这段跨越人机界限的友谊得以延续。',
created_at: '2024-03-20T10:01:00Z',
function_name: 'generate_script_summary',
custom_data: {
summary: '一个关于友谊和希望的温暖故事'
},
status: 'success',
intent_type: 'procedure'
},
// 角色生成 - 艾米丽
{
id: 4,
role: 'system',
content: '主角艾米丽的形象已生成',
created_at: '2024-03-20T10:02:00Z',
function_name: 'generate_character',
custom_data: {
character_name: '艾米丽',
image_path: 'https://picsum.photos/seed/emily/300/400',
completed_count: 1,
total_count: 2
},
status: 'success',
intent_type: 'procedure'
},
// 角色生成 - 小星
{
id: 5,
role: 'system',
content: '机器人小星的形象已生成',
created_at: '2024-03-20T10:03:00Z',
function_name: 'generate_character',
custom_data: {
character_name: '小星',
image_path: 'https://picsum.photos/seed/robot/300/400',
completed_count: 2,
total_count: 2
},
status: 'success',
intent_type: 'procedure'
},
// 场景生成 - 未来城市
{
id: 6,
role: 'system',
content: '未来城市场景设计完成',
created_at: '2024-03-20T10:04:00Z',
function_name: 'generate_sketch',
custom_data: {
sketch_name: '未来城市街景',
image_path: 'https://picsum.photos/seed/city/600/400',
completed_count: 1,
total_count: 3
},
status: 'success',
intent_type: 'procedure'
},
// 场景生成 - 艾米丽的房间
{
id: 7,
role: 'system',
content: '艾米丽的未来风格卧室设计完成',
created_at: '2024-03-20T10:05:00Z',
function_name: 'generate_sketch',
custom_data: {
sketch_name: '艾米丽的卧室',
image_path: 'https://picsum.photos/seed/room/600/400',
completed_count: 2,
total_count: 3
},
status: 'success',
intent_type: 'procedure'
},
// 场景生成 - 实验室
{
id: 8,
role: 'system',
content: '高科技实验室场景设计完成',
created_at: '2024-03-20T10:06:00Z',
function_name: 'generate_sketch',
custom_data: {
sketch_name: '未来实验室',
image_path: 'https://picsum.photos/seed/lab/600/400',
completed_count: 3,
total_count: 3
},
status: 'success',
intent_type: 'procedure'
},
// 分镜生成 - 相遇
{
id: 9,
role: 'system',
content: '第一个分镜:艾米丽收到礼物时的场景',
created_at: '2024-03-20T10:07:00Z',
function_name: 'generate_shot_sketch',
custom_data: {
shot_type: '中景',
atmosphere: '温馨、期待',
key_action: '艾米丽惊喜地打开礼物盒,小星缓缓启动',
url: 'https://picsum.photos/seed/shot1/600/400',
completed_count: 1,
total_count: 3
},
status: 'success',
intent_type: 'procedure'
},
// 分镜生成 - 危机
{
id: 10,
role: 'system',
content: '第二个分镜:小星能源耗尽的场景',
created_at: '2024-03-20T10:08:00Z',
function_name: 'generate_shot_sketch',
custom_data: {
shot_type: '特写',
atmosphere: '紧张、担忧',
key_action: '小星的能源指示灯闪烁微弱,艾米丽神情焦急',
url: 'https://picsum.photos/seed/shot2/600/400',
completed_count: 2,
total_count: 3
},
status: 'success',
intent_type: 'procedure'
},
// 分镜生成 - 解决
{
id: 11,
role: 'system',
content: '第三个分镜:找到新能源解决方案的场景',
created_at: '2024-03-20T10:09:00Z',
function_name: 'generate_shot_sketch',
custom_data: {
shot_type: '全景',
atmosphere: '欢欣、胜利',
key_action: '实验室中艾米丽成功激活新能源,小星重新焕发活力',
url: 'https://picsum.photos/seed/shot3/600/400',
completed_count: 3,
total_count: 3
},
status: 'success',
intent_type: 'procedure'
},
// 分镜视频生成
{
id: 11.1,
role: 'system',
content: '分镜视频生成完成',
created_at: '2024-03-20T10:10:00Z',
function_name: 'generate_video',
custom_data: {
prompt_json: {
core_atmosphere: '欢欣、胜利',
},
urls: ['https://cdn.qikongjian.com/faces/1755798635_facefusion_output_1755798635.mp4'],
completed_count: 1,
total_count: 1
},
status: 'success',
intent_type: 'procedure'
},
// 用户反馈
{
id: 12,
role: 'user',
content: JSON.stringify([{
type: 'text',
content: '这个故事设计太棒了!特别喜欢艾米丽和小星的互动场景。'
}]),
created_at: '2024-03-20T10:10:00Z',
function_name: undefined,
custom_data: undefined,
status: 'success',
intent_type: 'function_call'
},
// 助手回复
{
id: 13,
role: 'assistant',
content: JSON.stringify([{
type: 'text',
content: '谢谢您的肯定!我们可以继续优化任何场景或角色设计,您觉得有什么地方需要调整吗?'
}]),
created_at: '2024-03-20T10:10:10Z',
function_name: undefined,
custom_data: undefined,
status: 'success',
intent_type: 'function_call'
}
];
/**
* 类型守卫函数
*/
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;
}
/**
* 系统消息转换为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: `🎬 根据您输入的 "${customData.project_data.script}",我已完成项目的初始化。\n\n${content}`
}];
}
break;
case 'generate_script_summary':
if (isScriptSummary(customData)) {
blocks = [
{ type: 'text', text: `🎬 剧本摘要生成完成\n\n${customData.summary}\n\n${content}` }
];
}
break;
case 'generate_character':
if (isCharacterGeneration(customData)) {
blocks = [{
type: 'text',
text: `🎭 演员 "${customData.character_name}" 已就位`
}, {
type: 'image',
url: customData.image_path
}, {
type: 'text',
text: '图片中演员形象仅供参考,后续可根据视频生成后进行调整。'
}, {
type: 'progress',
value: customData.completed_count,
total: customData.total_count,
label: `已生成 ${customData.completed_count} 个演员,剧本中共有 ${customData.total_count}`
}, {
type: 'text',
text: `\n\n${content}`
}];
}
break;
case 'generate_sketch':
if (isSketchGeneration(customData)) {
blocks = [{
type: 'text',
text: `🎨 场景 "${customData.sketch_name}" 参考图片已生成 \n`
}, {
type: 'image',
url: customData.image_path
}, {
type: 'text',
text: '图片中场景仅供参考,后续可根据视频生成后进行调整。'
}, {
type: 'progress',
value: customData.completed_count,
total: customData.total_count,
label: `已生成 ${customData.completed_count} 个场景,剧本中共有 ${customData.total_count}`
}, {
type: 'text',
text: `\n\n${content}`
}];
}
break;
case 'generate_shot_sketch':
if (isShotSketchGeneration(customData)) {
blocks = [{
type: 'text',
text: `🎬 故事板静帧生成 \n镜头类型${customData.shot_type}\n氛围${customData.atmosphere}\n关键动作${customData.key_action}`
}, {
type: 'image',
url: customData.url
}, {
type: 'text',
text: '图片中故事板静帧仅供参考,后续可根据视频生成后进行调整。'
}, {
type: 'progress',
value: customData.completed_count,
total: customData.total_count,
label: `已生成 ${customData.completed_count} 个故事板静帧,剧本中共有 ${customData.total_count}`
}, {
type: 'text',
text: `\n\n${content}`
}];
}
break;
case 'generate_video':
if (isShotVideoGeneration(customData)) {
blocks.push({
type: 'text',
text: `🎬 该分镜下包含${customData.urls.length} 个视频。 \n核心氛围${customData.prompt_json.core_atmosphere}`
});
customData.urls.forEach((url: string) => {
blocks.push({
type: 'video',
url: url
});
});
blocks.push({
type: 'text',
text: '后续可在剪辑线上进行编辑。'
}, {
type: 'progress',
value: customData.completed_count,
total: customData.total_count,
label: `已生成 ${customData.completed_count} 个分镜视频,剧本中共有 ${customData.total_count}`
}, {
type: 'text',
text: `\n\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',
};
if (role === 'assistant' || role === 'user') {
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 (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: "无内容" });
}
return message;
} catch (error) {
console.error("转换消息格式失败:", error, apiMessage);
// 返回一个带有错误信息的消息
return {
id: new Date().getTime().toString(),
role: apiMessage.role,
createdAt: new Date(apiMessage.created_at).getTime(),
blocks: [{ type: "text", text: "消息格式错误" }],
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('发送历史消息请求:', request);
const response = await post<ApiResponse<MessagesResponse>>("/intelligent/history", request);
console.log('收到历史消息响应:', response);
// 确保 response.data 和 messages 存在
if (!response.data || !response.data.messages) {
console.error('历史消息响应格式错误:', response);
return {
messages: [],
hasMore: false,
totalCount: 0
};
}
// 转换消息并按时间排序
// if (response.data.messages.length === 0) {
// return {
// messages: MOCK_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("获取消息历史失败:", error);
throw error;
}
}
/**
* 发送新消息
*/
export async function sendMessage(
blocks: MessageBlock[],
config: ChatConfig
): Promise<void> {
// 提取文本和图片
const textBlocks = blocks.filter(b => b.type === "text");
const imageBlocks = blocks.filter(b => b.type === "image");
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;
}
try {
console.log('发送消息请求:', request);
await post<ApiResponse<RealApiMessage>>("/intelligent/chat", request);
} catch (error) {
console.error("发送消息失败:", error);
throw error;
}
}
/**
* 重试发送消息
*/
export async function retryMessage(
messageId: string,
config: ChatConfig
): Promise<void> {
// TODO: 实现实际的重试逻辑,可能需要保存原始消息内容
// 这里简单重用发送消息的接口
return sendMessage([{ type: "text", text: "重试消息" }], config);
}