import type { ChatBizResult, ChatCompletionsRequest, ChatCompletionsResponse, ChatStreamCallbacks, GetHistoriesRequest, GetHistoriesResponse, GetPaginationRecordsRequest, GetPaginationRecordsResponse, } from '@af-mobile-client-vue3/components/common/MateChat/types' import type { AxiosInstance } from 'axios' import { useUserStore } from '@af-mobile-client-vue3/stores/modules/user' import axios from 'axios' /** * 创建独立的 axios 实例,不经过拦截器 */ const chatAxiosInstance: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_APP_API_BASE_URL || '', timeout: 20000, headers: { 'Content-Type': 'application/json', }, }) /** * 生成 outLinkUid:用户类型 + 用户ID * @returns outLinkUid 字符串,格式:用户类型_用户ID */ export function generateOutLinkUid(): string { const userStore = useUserStore() const userInfo = userStore.getUserInfo() const userType = userStore.getUserType() // 用户类型:SYSTEM 或 EXTERNAL,如果为 null 则默认为 SYSTEM const type = userType || 'SYSTEM' // 用户ID const userId = userInfo?.id || '' // 兼容旧版af-system 返回 userinfoid 都一样的问题 if (type === 'EXTERNAL' && userInfo?.nickname) { return `EXTERNAL_${userInfo?.nickname}` } return `${type}_${userId}` } /** * 获取用户平台 OpenID * @returns OpenID */ export function getOpenId(): string { const userStore = useUserStore() const userInfo = userStore.getUserInfo() if (userInfo.platformUserId) { return userInfo.platformUserId } return null } /** * 发送聊天请求 * @param content 用户输入的内容 * @param appId FastGPT 应用 ID * @param appKey FastGPT API Key * @param chatId 会话 ID * @returns Promise */ export function chatCompletions( content: string, appId: string, appKey: string, chatId: string, ): Promise { const requestData: ChatCompletionsRequest = { chatId, stream: false, detail: false, variables: { openid: getOpenId(), // openid: 'oUx6l5gyHp3JNt9465QCKZOgjB9E', }, messages: [ { role: 'user', content, }, ], customUid: generateOutLinkUid(), } return chatAxiosInstance.post( '/fastApi/v1/chat/completions', requestData, { headers: { Authorization: `Bearer ${appKey}`, }, }, ).then(response => response.data) } /** * 封装后的业务聊天请求 * - 负责从大模型返回中解析出业务含义(普通回复 / 转人工) * - UI 层只需要根据 type 决定渲染哪种气泡 * @param content 用户输入的内容 * @param appId FastGPT 应用 ID * @param appKey FastGPT API Key * @param chatId 会话 ID */ export async function chatBiz( content: string, appId: string, appKey: string, chatId: string, ): Promise { const response = await chatCompletions(content, appId, appKey, chatId) if (!response.choices || response.choices.length === 0) { throw new Error('响应数据格式错误') } const text = response.choices[0].message.content try { const parsed = JSON.parse(text) as { msgType?: string } if (parsed.msgType === 'transfer') { return { type: 'transfer', content: text, } } } catch { // 忽略解析错误,按普通文本处理 } return { type: 'normal', content: text, } } /** * 使用 FastGPT /v1/chat/completions 的 stream=true 进行流式对话 * 参考官方文档: https://doc.fastgpt.io/docs/introduction/development/openapi/chat * @param content 用户输入的内容 * @param appId FastGPT 应用 ID * @param appKey FastGPT API Key * @param chatId 会话 ID * @param callbacks 流式回调函数 */ export async function chatCompletionsStream( content: string, appId: string, appKey: string, chatId: string, callbacks: ChatStreamCallbacks, ): Promise { const { onMessage, onComplete, onError } = callbacks const requestData: ChatCompletionsRequest = { chatId, stream: true, detail: false, variables: { openid: getOpenId(), // openid: 'oYt6WtxACKSH4XmIe0E3jUvx70U0', }, messages: [ { role: 'user', content, }, ], customUid: generateOutLinkUid(), } try { const baseUrl = import.meta.env.VITE_APP_API_BASE_URL || '' const res = await fetch(`${baseUrl}/fastApi/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${appKey}`, }, body: JSON.stringify(requestData), }) if (!res.ok || !res.body) { throw new Error(`请求失败: ${res.status}`) } const reader = res.body.getReader() const decoder = new TextDecoder('utf-8') let buffer = '' while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const lines = buffer.split('\n') buffer = lines.pop() ?? '' for (const rawLine of lines) { const line = rawLine.trim() if (!line.startsWith('data:')) continue const dataStr = line.slice(5).trim() if (!dataStr || dataStr === '[DONE]') { onComplete?.() return } try { const json = JSON.parse(dataStr) as { choices?: Array<{ delta?: { content?: string } }> } const delta = json.choices?.[0]?.delta const chunkText = delta?.content || '' if (chunkText) { onMessage?.(chunkText) } } catch (e) { console.error('解析数据失败', e, dataStr) } } } onComplete?.() } catch (error) { console.error('FastGPT 流式请求异常', error) onError?.(error) } } /** * 获取历史会话列表 * @param appId FastGPT 应用 ID * @param appKey FastGPT API Key * @param offset 偏移量 * @param pageSize 每页数量 * @returns Promise */ export function getHistories( appId: string, appKey: string, offset: number = 0, pageSize: number = 5, ): Promise { const requestData: GetHistoriesRequest = { appId, outLinkUid: generateOutLinkUid(), offset, pageSize, source: 'api', } return chatAxiosInstance.post( '/fastApi/core/chat/getHistories', requestData, { headers: { Authorization: `Bearer ${appKey}`, }, }, ).then(response => response.data) } /** * 获取历史会话记录 * @param appId FastGPT 应用 ID * @param appKey FastGPT API Key * @param chatId 会话 ID * @param offset 偏移量 * @param pageSize 每页数量 * @param loadCustomFeedbacks 是否加载自定义反馈 * @returns Promise */ export function getPaginationRecords( appId: string, appKey: string, chatId: string, offset: number = 0, pageSize: number = 10, loadCustomFeedbacks: boolean = true, ): Promise { const requestData: GetPaginationRecordsRequest = { appId, chatId, offset, pageSize, loadCustomFeedbacks, } return chatAxiosInstance.post( '/fastApi/core/chat/getPaginationRecords', requestData, { headers: { Authorization: `Bearer ${appKey}`, }, }, ).then(response => response.data) }