import type { Message } from '../types/types'; import { CozeAPI } from '@coze/api'; // 定义Coze事件数据接口 interface DeltaEventData { content?: string; delta?: { content?: string }; } interface CompletedEventData { conversation_id?: string; usage?: { token_count?: number }; token_count?: number; messages?: { content?: string }[]; } // 错误事件数据接口 - 备用 // interface ErrorEventData { // code?: string; // message?: string; // type?: string; // } class CozeService { private apiKey: string | null = null; private botId: string | null = null; private userId: string = `user_${Date.now()}`; // 生成一个临时用户ID private apiClient: CozeAPI | null = null; private conversationId: string | null = null; // 存储对话ID,用于后续调用 constructor() { // 初始化服务,实际使用时需要设置API密钥和Bot ID } /** * 设置API密钥和Bot ID * @param apiKey Coze API密钥 * @param botId Bot ID */ setCredentials(apiKey: string, botId: string): void { this.apiKey = apiKey; this.botId = botId; // 初始化Coze API客户端 - 严格按照官方示例实现 this.apiClient = new CozeAPI({ token: apiKey, baseURL: 'https://api.coze.cn' }); console.log('Coze API client initialized with official SDK'); } /** * 发送消息给Coze agent * 使用官方SDK的stream方法实现流式响应 * 严格按照api.ts示例实现,使用for await...of循环处理流式响应 * @param message 用户消息内容 * @param onStream 流式响应回调函数 * @returns Promise 完整的响应消息 */ async sendMessage( message: string, onStream?: (partialText: string, isDone: boolean) => void ): Promise { // 检查初始化状态 if (!this.isInitialized()) { console.warn('Coze服务尚未初始化或凭据不完整。使用模拟回复。'); return this.getMockResponse(message); } try { console.log('正在使用官方SDK发送消息到Coze服务...'); console.log('Bot ID:', this.botId); console.log('User ID:', this.userId); // 严格按照api.ts示例调用chat.stream方法 const res = await this.apiClient!.chat.stream({ bot_id: this.botId!, user_id: this.userId, conversation_id: this.conversationId || undefined, // 如果有保存的对话ID则使用 additional_messages: [ { "content": message, "content_type": "text", "role": "user", "type": "question" } ] as any // eslint-disable-line @typescript-eslint/no-explicit-any, }); let fullContent = ''; const messageId = Date.now().toString(); console.log('开始处理Coze流式响应...'); // 严格按照api.ts的实现方式,使用for await...of循环处理流式响应 for await (const event of res) { try { console.log('接收到Coze事件:', event.event || 'unknown'); // 1. 处理消息增量事件 if (event.event === 'conversation.message.delta') { // 根据TypeScript类型定义,content可能直接在data上 const deltaData = event.data as DeltaEventData; if (deltaData && deltaData.content) { const deltaContent = deltaData.content; fullContent += deltaContent; console.log('增量内容:', fullContent.substring(0, 100) + '...'); // 通知流式更新 if (onStream) { onStream(fullContent, false); } } else if (deltaData && deltaData.delta && deltaData.delta.content) { // 兼容另一种可能的数据结构 const deltaContent = deltaData.delta.content; fullContent += deltaContent; console.log('增量内容(delta路径):', fullContent.substring(0, 100) + '...'); if (onStream) { onStream(fullContent, false); } } } // 2. 处理聊天完成事件 if (event.event === 'conversation.chat.completed') { console.log(); // 输出换行 // 尝试从data中获取conversation_id和usage信息 const dataObj = event.data as CompletedEventData; // 打印conversation_id并保存 if (dataObj && dataObj.conversation_id) { console.log('Conversation ID:', dataObj.conversation_id); this.conversationId = dataObj.conversation_id; // 保存对话ID供下次使用 } // 打印token使用量 if (dataObj && dataObj.usage && dataObj.usage.token_count) { console.log('token usage:', dataObj.usage.token_count); } else if (dataObj && dataObj.token_count) { console.log('token usage:', dataObj.token_count); } // 尝试从data中获取完整内容(如果有的话) if (dataObj && dataObj.messages && dataObj.messages[0] && dataObj.messages[0].content) { fullContent = dataObj.messages[0].content || ''; console.log('使用完成事件中的完整内容'); } } // 3. 处理错误事件 if (event.event === 'error') { console.error('错误事件:', event.data); throw new Error(`Coze服务错误: ${JSON.stringify(event.data)}`); } } catch (err) { console.error('处理事件时出错:', err); console.log('错误事件完整数据:', JSON.stringify(event, null, 2)); } } // 流式处理完成,检查是否有内容 console.log('Coze流式响应处理完成,总内容长度:', fullContent.length); if (fullContent.trim()) { const botMessage: Message = { id: messageId, content: fullContent, sender: 'bot', timestamp: new Date() }; // 通知流式更新完成 if (onStream) { onStream(fullContent, true); } return botMessage; } else { throw new Error('Coze服务返回空内容,请检查API配置和网络连接'); } } catch (error) { console.error('Error sending message to Coze with official SDK:', error); // 获取具体的错误信息 const errorMsg = error instanceof Error ? error.message : '未知错误'; // 即使出错也返回模拟回复 return this.getMockResponse(message, errorMsg); } } /** * 获取模拟回复,当Coze API调用失败时使用 */ private getMockResponse(message: string, error?: string): Message { let mockContent = ''; // 根据用户消息内容生成更有针对性的模拟回复 if (message.includes('你好') || message.includes('hello') || message.includes('hi')) { mockContent = '你好!我是你的爆款文案策划师。为了给你提供更有针对性的方案,请告诉我:\n1. 你的行业/产品/服务\n2. 你的目标用户群体\n有了这些信息,我就能帮你打造更精准的文案了!'; } else if (message.includes('文案') || message.includes('内容') || message.includes('写作')) { mockContent = '关于文案创作,我有一些专业建议:\n1. 明确目标受众和核心卖点\n2. 使用吸引人的标题和开头\n3. 突出产品/服务的独特价值\n4. 加入社会证明(案例、数据)\n5. 包含明确的行动号召\n需要针对特定行业的文案模板吗?'; } else if (message.includes('价格') || message.includes('费用') || message.includes('多少钱')) { mockContent = '文案服务的价格因需求复杂度而异。我们提供:\n- 基础文案套餐:¥500-1000\n- 营销策划套餐:¥1000-3000\n- 品牌全案套餐:¥3000+\n欢迎告诉我你的具体需求,我可以为你提供更精准的报价。'; } else { mockContent = '感谢你的提问!作为文案策划师,我可以帮你:\n- 撰写吸引人的营销文案\n- 设计爆款标题\n- 优化产品描述\n- 制定内容营销策略\n请告诉我你具体需要哪方面的帮助?'; } // 如果有错误信息,可以选择性地在开头添加提示 if (error) { console.log('使用模拟回复,原因:', error); // 不直接显示错误,只显示友好提示 mockContent = '【提示:当前使用的是智能助手的推荐回复】\n\n' + mockContent; } return { id: Date.now().toString(), content: mockContent, sender: 'bot', timestamp: new Date() }; } /** * 重置对话状态 */ resetConversation(): void { this.conversationId = null; // 清除保存的对话ID console.log('Conversation reset'); } /** * 检查客户端是否已初始化 */ isInitialized(): boolean { return !!this.apiClient && !!this.apiKey && !!this.botId; } /** * 设置用户ID */ setUserId(userId: string): void { this.userId = userId; } /** * 获取用户ID */ getUserId(): string { return this.userId; } } // 导出单例实例 // 创建并导出单例实例 const cozeServiceInstance = new CozeService(); export default cozeServiceInstance;