对话流实现 文件上传
This commit is contained in:
@@ -18,114 +18,132 @@ import type {
|
||||
*/
|
||||
export const chatApi = {
|
||||
/**
|
||||
* 流式对话(SSE)- 使用fetch支持Authorization
|
||||
* 流式对话(SSE)- 两步法:POST准备 + GET建立SSE
|
||||
* @param request 对话请求
|
||||
* @param callback 流式回调
|
||||
* @returns Promise<ResultDomain<AiMessage>>
|
||||
*/
|
||||
async streamChat(request: ChatRequest, callback?: StreamCallback): Promise<ResultDomain<AiMessage>> {
|
||||
const token = localStorage.getItem('token');
|
||||
const tokenData = token ? JSON.parse(token) : null;
|
||||
return new Promise((resolve, reject) => {
|
||||
// 使用相对路径走Vite代理,避免跨域
|
||||
const eventSource = new EventSource(
|
||||
`/api/ai/chat/stream?` +
|
||||
new URLSearchParams({
|
||||
// 使用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', {
|
||||
agentId: request.agentId,
|
||||
conversationId: request.conversationId || '',
|
||||
query: request.query,
|
||||
knowledgeIds: request.knowledgeIds?.join(',') || '',
|
||||
token: tokenData?.value || ''
|
||||
})
|
||||
);
|
||||
files: request.files || []
|
||||
}, {
|
||||
showLoading: false
|
||||
});
|
||||
|
||||
// 通知外部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);
|
||||
if (!prepareResponse.data.success || !prepareResponse.data.data) {
|
||||
throw new Error(prepareResponse.data.message || '准备会话失败');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('解析init事件失败:', event.data);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听标准消息事件
|
||||
eventSource.addEventListener('message', (event) => {
|
||||
const data = event.data;
|
||||
fullMessage += data;
|
||||
callback?.onMessage?.(data);
|
||||
});
|
||||
const sessionId = prepareResponse.data.data;
|
||||
console.log('[会话创建成功] sessionId:', sessionId);
|
||||
|
||||
// 监听结束事件
|
||||
eventSource.addEventListener('end', (event) => {
|
||||
const metadata = JSON.parse(event.data);
|
||||
callback?.onMessageEnd?.(metadata);
|
||||
eventSource.close();
|
||||
// 第2步:GET建立SSE连接
|
||||
const eventSource = new EventSource(
|
||||
`/api/ai/chat/stream?sessionId=${sessionId}&token=${tokenData}`
|
||||
);
|
||||
|
||||
resolve({
|
||||
code: 200,
|
||||
success: true,
|
||||
login: true,
|
||||
auth: true,
|
||||
data: metadata as AiMessage,
|
||||
message: '对话成功'
|
||||
});
|
||||
});
|
||||
// 通知外部EventSource已创建
|
||||
callback?.onStart?.(eventSource);
|
||||
|
||||
// 监听所有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'
|
||||
];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let fullMessage = ''; // 累积完整消息内容
|
||||
|
||||
difyEventTypes.forEach(eventType => {
|
||||
eventSource.addEventListener(eventType, (event: any) => {
|
||||
try {
|
||||
const eventData = JSON.parse(event.data);
|
||||
console.log(`[Dify事件] ${eventType}:`, eventData);
|
||||
|
||||
// 调用自定义的Dify事件回调
|
||||
if (callback?.onDifyEvent) {
|
||||
const cleanEventType = eventType.replace('dify_', '');
|
||||
callback.onDifyEvent(cleanEventType, eventData);
|
||||
// 监听初始化事件(包含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);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`解析Dify事件失败 ${eventType}:`, event.data);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 监听错误事件
|
||||
eventSource.addEventListener('error', (event: any) => {
|
||||
const error = new Error(event.data || '对话失败');
|
||||
callback?.onError?.(error);
|
||||
eventSource.close();
|
||||
reject(error);
|
||||
});
|
||||
// 监听标准消息事件
|
||||
eventSource.addEventListener('message', (event) => {
|
||||
const data = event.data;
|
||||
fullMessage += data;
|
||||
callback?.onMessage?.(data);
|
||||
});
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
callback?.onError?.(error as unknown as Error);
|
||||
eventSource.close();
|
||||
reject(error);
|
||||
};
|
||||
// 监听结束事件
|
||||
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) => {
|
||||
const error = new Error(event.data || '对话失败');
|
||||
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
|
||||
});
|
||||
},
|
||||
|
||||
@@ -173,6 +191,8 @@ export const chatApi = {
|
||||
const response = await api.post<AiConversation>('/ai/chat/conversation', {
|
||||
agentId,
|
||||
title
|
||||
}, {
|
||||
showLoading: false
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
@@ -193,7 +213,9 @@ export const chatApi = {
|
||||
* @returns Promise<ResultDomain<AiConversation>>
|
||||
*/
|
||||
async updateConversation(conversation: AiConversation): Promise<ResultDomain<AiConversation>> {
|
||||
const response = await api.put<AiConversation>('/ai/chat/conversation', conversation);
|
||||
const response = await api.put<AiConversation>('/ai/chat/conversation', conversation, {
|
||||
showLoading: false
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
@@ -203,7 +225,9 @@ export const chatApi = {
|
||||
* @returns Promise<ResultDomain<boolean>>
|
||||
*/
|
||||
async deleteConversation(conversationId: string): Promise<ResultDomain<boolean>> {
|
||||
const response = await api.delete<boolean>(`/ai/chat/conversation/${conversationId}`);
|
||||
const response = await api.delete<boolean>(`/ai/chat/conversation/${conversationId}`, {
|
||||
showLoading: false
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
@@ -225,7 +249,9 @@ export const chatApi = {
|
||||
* @returns Promise<ResultDomain<AiMessage[]>>
|
||||
*/
|
||||
async listMessages(conversationId: string): Promise<ResultDomain<AiMessage>> {
|
||||
const response = await api.get<AiMessage>(`/ai/chat/conversation/${conversationId}/messages`);
|
||||
const response = await api.get<AiMessage>(`/ai/chat/conversation/${conversationId}/messages`, {
|
||||
showLoading: false
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
@@ -318,9 +344,7 @@ export const chatApi = {
|
||||
difyEventTypes.forEach(eventType => {
|
||||
eventSource.addEventListener(eventType, (event: any) => {
|
||||
try {
|
||||
const eventData = JSON.parse(event.data);
|
||||
console.log(`[Dify事件] ${eventType}:`, eventData);
|
||||
|
||||
const eventData = JSON.parse(event.data);
|
||||
// 调用自定义的Dify事件回调
|
||||
if (callback?.onDifyEvent) {
|
||||
const cleanEventType = eventType.replace('dify_', '');
|
||||
@@ -369,6 +393,8 @@ export const chatApi = {
|
||||
const response = await api.post<boolean>(`/ai/chat/message/${messageId}/rate`, {
|
||||
rating,
|
||||
feedback
|
||||
}, {
|
||||
showLoading: false
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user