first commit
This commit is contained in:
2
app/service/api.ts
Normal file
2
app/service/api.ts
Normal file
@ -0,0 +1,2 @@
|
||||
// Coze API服务文件
|
||||
// 注意:测试函数和未使用的接口已移除,以避免类型冲突问题
|
||||
259
app/service/cozeService.ts
Normal file
259
app/service/cozeService.ts
Normal file
@ -0,0 +1,259 @@
|
||||
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<Message> 完整的响应消息
|
||||
*/
|
||||
async sendMessage(
|
||||
message: string,
|
||||
onStream?: (partialText: string, isDone: boolean) => void
|
||||
): Promise<Message> {
|
||||
// 检查初始化状态
|
||||
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;
|
||||
85
app/service/initializeCoze.ts
Normal file
85
app/service/initializeCoze.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import cozeService from './cozeService';
|
||||
import type { CozeConfig } from '../types/types';
|
||||
|
||||
/**
|
||||
* 初始化Coze服务
|
||||
* 这个函数应该在客户端组件中调用,因为API密钥需要在客户端安全管理
|
||||
*
|
||||
* @param config Coze配置信息,包含API密钥和Bot ID
|
||||
* @returns 是否初始化成功
|
||||
*/
|
||||
export const initializeCoze = (config: CozeConfig): boolean => {
|
||||
try {
|
||||
// 验证配置
|
||||
if (!config.apiKey || !config.botId) {
|
||||
console.error('Missing Coze API key or Bot ID');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置凭证并初始化客户端
|
||||
cozeService.setCredentials(config.apiKey, config.botId);
|
||||
|
||||
console.log('Coze service initialized successfully');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize Coze service:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 从环境变量或localStorage加载Coze配置
|
||||
* 在实际应用中,建议使用更安全的方式存储API密钥
|
||||
*/
|
||||
export const loadCozeConfig = (): CozeConfig | null => {
|
||||
try {
|
||||
// 在实际应用中,你可能会从环境变量或安全存储中获取这些值
|
||||
// 这里我们从localStorage中读取作为示例
|
||||
const storedConfig = localStorage.getItem('cozeConfig');
|
||||
if (storedConfig) {
|
||||
return JSON.parse(storedConfig);
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Failed to load Coze configuration:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存Coze配置到localStorage
|
||||
* 在实际应用中,建议使用更安全的方式存储API密钥
|
||||
*/
|
||||
export const saveCozeConfig = (config: CozeConfig): boolean => {
|
||||
try {
|
||||
localStorage.setItem('cozeConfig', JSON.stringify(config));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to save Coze configuration:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取已初始化的Coze服务实例
|
||||
* @returns CozeService实例
|
||||
*/
|
||||
export function getCozeService() {
|
||||
return cozeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Coze服务是否已初始化
|
||||
* @returns boolean 是否已初始化
|
||||
*/
|
||||
export function isCozeInitialized(): boolean {
|
||||
return cozeService.isInitialized();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置Coze对话
|
||||
*/
|
||||
export function resetCozeConversation(): void {
|
||||
cozeService.resetConversation();
|
||||
}
|
||||
Reference in New Issue
Block a user