Files
schoolNews/schoolNewsWeb/src/apis/ai/chat.ts

459 lines
14 KiB
TypeScript
Raw Normal View History

2025-11-04 18:49:37 +08:00
/**
* @description AI对话相关API
* @author AI Assistant
* @since 2025-11-04
*/
import { api } from '@/apis/index';
2025-11-26 15:01:25 +08:00
import { API_BASE_URL } from '@/config';
2025-11-04 18:49:37 +08:00
import type {
AiConversation,
AiMessage,
ChatRequest,
ResultDomain,
StreamCallback
} from '@/types';
/**
* AI对话API服务
*/
export const chatApi = {
/**
2025-11-06 16:43:28 +08:00
* SSE- POST准备 + GET建立SSE
2025-11-04 18:49:37 +08:00
* @param request
* @param callback
* @returns Promise<ResultDomain<AiMessage>>
*/
async streamChat(request: ChatRequest, callback?: StreamCallback): Promise<ResultDomain<AiMessage>> {
return new Promise((resolve, reject) => {
2025-11-06 16:43:28 +08:00
// 使用IIFE包装async逻辑避免Promise executor是async的警告
(async () => {
try {
const token = localStorage.getItem('token');
const tokenData = token ? JSON.parse(token).value : '';
// 第1步POST准备会话获取sessionId
const prepareResponse = await api.post<string>('/ai/chat/stream/prepare', {
2025-11-04 18:49:37 +08:00
agentId: request.agentId,
conversationId: request.conversationId || '',
query: request.query,
2025-11-06 16:43:28 +08:00
files: request.files || []
}, {
showLoading: false
});
2025-11-04 18:49:37 +08:00
2025-11-06 16:43:28 +08:00
if (!prepareResponse.data.success || !prepareResponse.data.data) {
throw new Error(prepareResponse.data.message || '准备会话失败');
2025-11-05 16:55:58 +08:00
}
2025-11-04 18:49:37 +08:00
2025-11-06 16:43:28 +08:00
const sessionId = prepareResponse.data.data;
console.log('[会话创建成功] sessionId:', sessionId);
// 第2步GET建立SSE连接
const eventSource = new EventSource(
2025-11-26 15:01:25 +08:00
`${API_BASE_URL}/ai/chat/stream?sessionId=${sessionId}&token=${tokenData}`
2025-11-06 16:43:28 +08:00
);
// 通知外部EventSource已创建
callback?.onStart?.(eventSource);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let fullMessage = ''; // 累积完整消息内容
// 监听初始化事件包含messageId和conversationId
eventSource.addEventListener('init', (event) => {
try {
const initData = JSON.parse(event.data);
console.log('[初始化数据]', initData);
// 通知外部保存messageId用于停止生成
if (callback?.onInit) {
callback.onInit(initData);
}
} catch (e) {
console.warn('解析init事件失败:', event.data);
2025-11-05 16:55:58 +08:00
}
2025-11-06 16:43:28 +08:00
});
// 监听标准消息事件
eventSource.addEventListener('message', (event) => {
const data = event.data;
fullMessage += data;
callback?.onMessage?.(data);
});
// 监听结束事件
eventSource.addEventListener('end', (event) => {
const metadata = JSON.parse(event.data);
callback?.onMessageEnd?.(metadata);
eventSource.close();
resolve({
code: 200,
success: true,
login: true,
auth: true,
data: metadata as AiMessage,
message: '对话成功'
});
});
// 监听所有Dify原始事件workflow_started, node_started等
const difyEventTypes = [
'dify_workflow_started',
'dify_node_started',
'dify_node_finished',
'dify_workflow_finished',
'dify_message',
'dify_agent_message',
'dify_message_end',
'dify_message_file',
'dify_agent_thought',
'dify_ping'
];
difyEventTypes.forEach(eventType => {
eventSource.addEventListener(eventType, (event: any) => {
try {
const eventData = JSON.parse(event.data);
// 调用自定义的Dify事件回调
if (callback?.onDifyEvent) {
const cleanEventType = eventType.replace('dify_', '');
callback.onDifyEvent(cleanEventType, eventData);
}
} catch (e) {
console.warn(`解析Dify事件失败 ${eventType}:`, event.data);
}
});
});
// 监听错误事件
eventSource.addEventListener('error', (event: any) => {
2025-11-17 18:34:37 +08:00
// 如果 data 为空说明是正常的流结束信号有些工作流不发送end事件
if (!event.data || event.data.trim() === '') {
console.log('[SSE流结束] 收到空error事件当作正常结束处理');
// 触发结束回调(如果有的话)
callback?.onMessageEnd?.({
conversationId: request.conversationId,
messageId: '',
answer: fullMessage
} as any);
eventSource.close();
// 正常结束
resolve({
code: 200,
success: true,
login: true,
auth: true,
data: {
conversationId: request.conversationId,
answer: fullMessage
} as any,
message: '对话已结束'
});
return;
}
const error = new Error(event.data);
2025-11-06 16:43:28 +08:00
callback?.onError?.(error);
eventSource.close();
reject(error);
});
eventSource.onerror = (error) => {
callback?.onError?.(error as unknown as Error);
eventSource.close();
reject(error);
};
} catch (error) {
console.error('流式对话失败:', error);
callback?.onError?.(error as Error);
reject(error);
}
})(); // 立即执行IIFE
2025-11-04 18:49:37 +08:00
});
},
/**
*
* @param request
* @returns Promise<ResultDomain<AiMessage>>
*/
async blockingChat(request: ChatRequest): Promise<ResultDomain<AiMessage>> {
const response = await api.post<AiMessage>('/ai/chat/blocking', request);
return response.data;
},
/**
2025-11-05 16:55:58 +08:00
* ID
2025-11-04 18:49:37 +08:00
* @param messageId ID
* @returns Promise<ResultDomain<boolean>>
*/
async stopChat(messageId: string): Promise<ResultDomain<boolean>> {
const response = await api.post<boolean>(`/ai/chat/stop/${messageId}`);
return response.data;
},
2025-11-05 16:55:58 +08:00
/**
* Dify TaskID
* @param taskId Dify任务ID
* @param agentId ID
* @returns Promise<ResultDomain<boolean>>
*/
async stopChatByTaskId(taskId: string, agentId: string): Promise<ResultDomain<boolean>> {
const response = await api.post<boolean>('/ai/chat/stop-by-taskid', {
taskId,
agentId
});
return response.data;
},
2025-11-04 18:49:37 +08:00
/**
*
* @param agentId ID
* @param title
* @returns Promise<ResultDomain<AiConversation>>
*/
async createConversation(agentId: string, title?: string): Promise<ResultDomain<AiConversation>> {
const response = await api.post<AiConversation>('/ai/chat/conversation', {
agentId,
title
2025-11-06 16:43:28 +08:00
}, {
showLoading: false
2025-11-04 18:49:37 +08:00
});
return response.data;
},
/**
*
* @param conversationId ID
* @returns Promise<ResultDomain<AiConversation>>
*/
async getConversation(conversationId: string): Promise<ResultDomain<AiConversation>> {
const response = await api.get<AiConversation>(`/ai/chat/conversation/${conversationId}`);
return response.data;
},
/**
*
* @param conversation
* @returns Promise<ResultDomain<AiConversation>>
*/
async updateConversation(conversation: AiConversation): Promise<ResultDomain<AiConversation>> {
2025-11-06 16:43:28 +08:00
const response = await api.put<AiConversation>('/ai/chat/conversation', conversation, {
showLoading: false
});
2025-11-04 18:49:37 +08:00
return response.data;
},
/**
*
* @param conversationId ID
* @returns Promise<ResultDomain<boolean>>
*/
async deleteConversation(conversationId: string): Promise<ResultDomain<boolean>> {
2025-11-08 10:56:32 +08:00
const response = await api.delete<boolean>(`/ai/chat/conversation/${conversationId}`,{}, {
2025-11-06 16:43:28 +08:00
showLoading: false
});
2025-11-04 18:49:37 +08:00
return response.data;
},
/**
*
* @param agentId ID
* @returns Promise<ResultDomain<AiConversation[]>>
*/
async listUserConversations(agentId?: string): Promise<ResultDomain<AiConversation[]>> {
const response = await api.get<AiConversation[]>('/ai/chat/conversations', {
params: { agentId }
});
return response.data;
},
/**
*
* @param conversationId ID
* @returns Promise<ResultDomain<AiMessage[]>>
*/
2025-11-05 16:55:58 +08:00
async listMessages(conversationId: string): Promise<ResultDomain<AiMessage>> {
2025-11-08 10:56:32 +08:00
const response = await api.get<AiMessage>(`/ai/chat/conversation/${conversationId}/messages`, {},{
2025-11-06 16:43:28 +08:00
showLoading: false
});
2025-11-04 18:49:37 +08:00
return response.data;
},
/**
*
* @param messageId ID
* @returns Promise<ResultDomain<AiMessage>>
*/
async getMessage(messageId: string): Promise<ResultDomain<AiMessage>> {
const response = await api.get<AiMessage>(`/ai/chat/message/${messageId}`);
return response.data;
},
/**
2025-11-05 16:55:58 +08:00
* SSE流式
2025-11-04 18:49:37 +08:00
* @param messageId ID
2025-11-05 16:55:58 +08:00
* @param callback
2025-11-04 18:49:37 +08:00
* @returns Promise<ResultDomain<AiMessage>>
*/
async regenerateAnswer(messageId: string, callback?: StreamCallback): Promise<ResultDomain<AiMessage>> {
2025-11-05 16:55:58 +08:00
const token = localStorage.getItem('token');
const tokenData = token ? JSON.parse(token) : null;
return new Promise((resolve, reject) => {
2025-11-26 15:01:25 +08:00
// 使用配置的baseURLSSE流式推送
2025-11-05 16:55:58 +08:00
const eventSource = new EventSource(
2025-11-26 15:01:25 +08:00
`${API_BASE_URL}/ai/chat/regenerate/${messageId}?` +
2025-11-05 16:55:58 +08:00
new URLSearchParams({
token: tokenData?.value || ''
})
);
// 通知外部EventSource已创建
callback?.onStart?.(eventSource);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let fullMessage = ''; // 累积完整消息内容
// 监听初始化事件包含messageId和conversationId
eventSource.addEventListener('init', (event) => {
try {
const initData = JSON.parse(event.data);
console.log('[初始化数据-重新生成]', initData);
// 通知外部保存messageId用于停止生成
if (callback?.onInit) {
callback.onInit(initData);
}
} catch (e) {
console.warn('解析init事件失败:', event.data);
}
});
// 监听标准消息事件
eventSource.addEventListener('message', (event) => {
const data = event.data;
fullMessage += data;
callback?.onMessage?.(data);
});
// 监听结束事件
eventSource.addEventListener('end', (event) => {
const metadata = JSON.parse(event.data);
callback?.onMessageEnd?.(metadata);
eventSource.close();
2025-11-04 18:49:37 +08:00
2025-11-05 16:55:58 +08:00
resolve({
code: 200,
success: true,
login: true,
auth: true,
data: metadata as AiMessage,
message: '重新生成成功'
2025-11-04 18:49:37 +08:00
});
2025-11-05 16:55:58 +08:00
});
2025-11-04 18:49:37 +08:00
2025-11-05 16:55:58 +08:00
// 监听所有Dify原始事件workflow_started, node_started等
const difyEventTypes = [
'dify_workflow_started',
'dify_node_started',
'dify_node_finished',
'dify_workflow_finished',
'dify_message',
'dify_agent_message',
'dify_message_end',
'dify_message_file',
'dify_agent_thought',
'dify_ping'
];
difyEventTypes.forEach(eventType => {
eventSource.addEventListener(eventType, (event: any) => {
try {
2025-11-06 16:43:28 +08:00
const eventData = JSON.parse(event.data);
2025-11-05 16:55:58 +08:00
// 调用自定义的Dify事件回调
if (callback?.onDifyEvent) {
const cleanEventType = eventType.replace('dify_', '');
callback.onDifyEvent(cleanEventType, eventData);
}
} catch (e) {
console.warn(`解析Dify事件失败 ${eventType}:`, event.data);
}
2025-11-04 18:49:37 +08:00
});
});
2025-11-05 16:55:58 +08:00
// 监听错误事件
eventSource.addEventListener('error', (event: any) => {
2025-11-17 18:34:37 +08:00
// 如果 data 为空说明是正常的流结束信号有些工作流不发送end事件
if (!event.data || event.data.trim() === '') {
console.log('[SSE流结束-重新生成] 收到空error事件当作正常结束处理');
// 触发结束回调(如果有的话)
callback?.onMessageEnd?.({
messageId: messageId,
answer: fullMessage
} as any);
eventSource.close();
// 正常结束
resolve({
code: 200,
success: true,
login: true,
auth: true,
data: {
messageId: messageId,
answer: fullMessage
} as any,
message: '重新生成已结束'
});
return;
}
const error = new Error(event.data);
2025-11-05 16:55:58 +08:00
callback?.onError?.(error);
eventSource.close();
reject(error);
});
eventSource.onerror = (error) => {
callback?.onError?.(error as unknown as Error);
eventSource.close();
reject(error);
};
});
2025-11-04 18:49:37 +08:00
},
/**
*
* @param conversationId ID
* @returns Promise<ResultDomain<boolean>>
*/
async generateSummary(conversationId: string): Promise<ResultDomain<boolean>> {
const response = await api.post<boolean>(`/ai/chat/conversation/${conversationId}/summary`);
return response.data;
},
/**
*
* @param messageId ID
* @param rating 1=-1=0=
* @param feedback
* @returns Promise<ResultDomain<boolean>>
*/
async rateMessage(messageId: string, rating: number, feedback?: string): Promise<ResultDomain<boolean>> {
const response = await api.post<boolean>(`/ai/chat/message/${messageId}/rate`, {
rating,
feedback
2025-11-06 16:43:28 +08:00
}, {
showLoading: false
2025-11-04 18:49:37 +08:00
});
return response.data;
}
};