Files
urbanLifeline/urbanLifelineWeb/packages/workcase_wechat/api/ai/aiChat.ts
2025-12-29 12:49:23 +08:00

212 lines
7.7 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 { request, uploadFile } from '../base'
import type { ResultDomain } from '../../types'
import type {
TbChat,
TbChatMessage,
CreateChatParam,
PrepareChatParam,
StopChatParam,
CommentMessageParam,
ChatListParam,
ChatMessageListParam,
SSECallbacks,
SSETask,
SSEMessageData,
DifyFileInfo
} from '../../types/ai/aiChat'
/* eslint-disable @typescript-eslint/no-explicit-any */
declare const uni: {
getStorageSync: (key: string) => any
request: (options: any) => any
}
// API 基础配置
const BASE_URL = 'http://localhost:8180'
/**
* @description AI对话相关接口直接调用ai模块
* @filename aiChat.ts
* @author cascade
* @copyright xyzh
* @since 2025-12-23
*/
export const aiChatAPI = {
baseUrl: '/urban-lifeline/ai/chat',
// ====================== AI对话管理 ======================
/**
* 创建对话
* @param param agentId、userId、title 必传
*/
createChat(param: CreateChatParam): Promise<ResultDomain<TbChat>> {
return request<TbChat>({ url: `${this.baseUrl}/conversation`, method: 'POST', data: param })
},
/**
* 更新对话
* @param chat agentId、userId、title、userType 必传
*/
updateChat(chat: TbChat): Promise<ResultDomain<TbChat>> {
return request<TbChat>({ url: `${this.baseUrl}/conversation`, method: 'PUT', data: chat })
},
/**
* 查询对话列表
* @param param agentId 必传
*/
getChatList(param: ChatListParam): Promise<ResultDomain<TbChat[]>> {
return request<TbChat[]>({ url: `${this.baseUrl}/conversations`, method: 'GET', data: param })
},
/**
* 获取对话消息列表
* @param param agentId、chatId、userId 必传
*/
getChatMessageList(param: ChatMessageListParam): Promise<ResultDomain<TbChatMessage[]>> {
return request<TbChatMessage[]>({ url: `${this.baseUrl}/messages`, method: 'POST', data: param })
},
/**
* 准备流式对话会话
* @param param agentId、chatId、query、userId 必传
*/
prepareChatMessageSession(param: PrepareChatParam): Promise<ResultDomain<string>> {
return request<string>({ url: `${this.baseUrl}/stream/prepare`, method: 'POST', data: param })
},
/**
* 流式对话SSE- 返回EventSource URL
*/
getStreamUrl(sessionId: string): string {
return `${this.baseUrl}/stream?sessionId=${sessionId}`
},
/**
* 建立SSE流式对话连接
* @param sessionId 会话ID必传
* @param callbacks 回调函数
* @returns SSETask 可用于中止请求
*/
streamChat(sessionId: string, callbacks: SSECallbacks): SSETask {
const url = `${BASE_URL}${this.baseUrl}/stream?sessionId=${sessionId}`
const token = uni.getStorageSync('token') || ''
const requestTask = uni.request({
url: url,
method: 'GET',
header: {
'Accept': 'text/event-stream',
'Authorization': token ? `Bearer ${token}` : ''
},
enableChunked: true,
success: (res: any) => {
console.log('SSE请求完成:', res)
// 处理非200状态码
if (res.statusCode !== 200) {
console.error('SSE请求状态码异常:', res.statusCode)
let errorMsg = '抱歉,服务暂时不可用,请稍后重试。'
if (res.statusCode === 401) {
errorMsg = '登录已过期,请重新登录。'
} else if (res.statusCode === 403) {
errorMsg = '无权限访问,请联系管理员。'
} else if (res.statusCode === 404) {
errorMsg = '会话不存在或已过期,请重新发起对话。'
} else if (res.statusCode >= 500) {
errorMsg = '服务器异常,请稍后重试。'
}
callbacks.onError?.(errorMsg)
}
callbacks.onComplete?.()
},
fail: (err: any) => {
console.error('SSE请求失败:', err)
callbacks.onError?.('网络连接失败,请稍后重试。')
callbacks.onComplete?.()
}
})
// 监听分块数据
requestTask.onChunkReceived((res: any) => {
try {
const decoder = new TextDecoder('utf-8')
const text = decoder.decode(new Uint8Array(res.data))
const lines = text.split('\n')
for (const line of lines) {
if (line.startsWith('data:')) {
const dataStr = line.substring(5).trim()
if (dataStr && dataStr !== '[DONE]') {
try {
const data: SSEMessageData = JSON.parse(dataStr)
const event = data.event
if (event === 'message' || event === 'agent_message') {
callbacks.onMessage?.(data)
} else if (event === 'message_end') {
callbacks.onEnd?.(data.task_id || '')
} else if (event === 'error' || data.message) {
// 解析错误消息,提取友好提示
let errorMsg = data.message || '发生错误,请稍后重试。'
// 处理嵌套的 JSON 错误信息
if (errorMsg.includes('invalid_param')) {
errorMsg = '请求参数错误,请稍后重试。'
} else if (errorMsg.includes('rate_limit')) {
errorMsg = '请求过于频繁,请稍后再试。'
} else if (errorMsg.includes('quota_exceeded')) {
errorMsg = 'AI 服务额度已用完,请联系管理员。'
}
callbacks.onError?.(errorMsg)
}
} catch (e) {
console.log('解析SSE数据失败:', dataStr)
}
}
}
}
} catch (e) {
console.error('处理分块数据失败:', e)
}
})
return {
abort: () => {
requestTask.abort()
}
}
},
/**
* 停止对话
* @param param taskId、agentId、userId 必传
*/
stopChat(param: StopChatParam): Promise<ResultDomain<boolean>> {
return request<boolean>({ url: `${this.baseUrl}/stop`, method: 'POST', data: param })
},
/**
* 评价对话消息
* @param param agentId、chatId、messageId、comment、userId 必传
*/
commentChatMessage(param: CommentMessageParam): Promise<ResultDomain<boolean>> {
return request<boolean>({ url: `${this.baseUrl}/comment`, method: 'POST', data: param })
},
// ====================== 文件上传 ======================
/**
* 上传文件用于对话(图文多模态)
* @param filePath 文件临时路径
* @param agentId 智能体ID
*/
uploadFileForChat(filePath: string, agentId: string): Promise<ResultDomain<DifyFileInfo>> {
return uploadFile<DifyFileInfo>({
url: `${this.baseUrl}/file/upload`,
filePath: filePath,
name: 'file',
formData: { agentId: agentId }
})
}
}