"use client"; import { REQUEST_TIMEOUT_MS } from "@/app/constant"; import { useChatStore } from "@/app/store"; import { ChatOptions, LLMApi, LLMModel, } from "../api"; import Locale from "../../locales"; import { EventStreamContentType, fetchEventSource, } from "@fortaine/fetch-event-source"; import { prettyObject } from "@/app/utils/format"; import { getMessageTextContent } from "@/app/utils"; import api from "@/app/api/api"; export class BigModelApi implements LLMApi { public baseURL: string; public apiPath: string; constructor() { const chatMode = useChatStore.getState().chatMode; // this.baseURL = 'http://xia0miduo.gicp.net:8401'; this.baseURL = '/bigmodel-api'; if (chatMode === 'LOCAL') { this.apiPath = this.baseURL + '/deepseek/api/chat'; } else { this.apiPath = this.baseURL + '/bigmodel/api/model-api/sse-invoke'; } } async chat(options: ChatOptions) { const messages = options.messages.map((item) => { return { role: item.role, content: getMessageTextContent(item), } }); const userMessages = messages.filter(item => item.content); if (userMessages.length % 2 === 0) { userMessages.unshift({ role: "user", content: "⠀", }); } // 参数 const params = { appId: options.config.appId,// 应用id prompt: userMessages, // 进阶配置 request_id: 'jkec2024-knowledge-base', returnType: undefined, knowledge_ids: undefined, document_ids: undefined, }; const controller = new AbortController(); options.onController?.(controller); try { const userInfo = localStorage.getItem('userInfo'); const token = userInfo ? JSON.parse(userInfo).token : ""; const chatPath = this.apiPath; const chatPayload = { method: "POST", body: JSON.stringify(params), signal: controller.signal, headers: { 'Content-Type': 'application/json', 'Authorization': token }, }; const requestTimeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); let responseText = ""; let remainText = ""; let finished = false; function animateResponseText() { if (finished || controller.signal.aborted) { responseText += remainText; if (responseText?.length === 0) { options.onError?.(new Error("请求已中止,请检查网络环境。")); } return; } if (remainText.length > 0) { const fetchCount = Math.max(1, Math.round(remainText.length / 60)); const fetchText = remainText.slice(0, fetchCount); responseText += fetchText; remainText = remainText.slice(fetchCount); options.onUpdate?.(responseText, fetchText); } requestAnimationFrame(animateResponseText); } animateResponseText(); const finish = () => { if (!finished) { finished = true; options.onFinish(responseText + remainText); } }; controller.signal.onabort = finish; let sliceInfoPromise: Promise | null = null; fetchEventSource(chatPath, { ...chatPayload, async onopen(res: any) { clearTimeout(requestTimeoutId); const contentType = res.headers.get("content-type"); if (contentType?.startsWith("text/plain")) { responseText = await res.clone().text(); return finish(); } if ( !res.ok || !res.headers.get("content-type")?.startsWith(EventStreamContentType) || res.status !== 200 ) { const responseTexts = [responseText]; let extraInfo = await res.clone().text(); try { const resJson = await res.clone().json(); extraInfo = prettyObject(resJson); } catch { } if (res.status === 401) { responseTexts.push(Locale.Error.Unauthorized); } if (extraInfo) { responseTexts.push(extraInfo); } responseText = responseTexts.join("\n\n"); return finish(); } }, onmessage: async (msg) => { const info = JSON.parse(msg.data); if (info.event === 'finish') { // 完成 const chatMode = useChatStore.getState().chatMode; if (chatMode === 'LOCAL') {// 切片 useChatStore.getState().updateCurrentSession((se) => { se.chat_id = info.id; }); sliceInfoPromise = (async () => { try { const res: any = await api.get(`deepseek/api/slice/search/${info.id}`); let allChunkNum = 0; delete res.data.code; const values1 = Object.keys(res.data).reduce((acc, knowledge_id) => { const docs = res.data[knowledge_id]; const transformedDocs = docs.map((doc: any) =>{ allChunkNum += doc.chunk_nums; return{ knowledge_id, ...doc } }); return acc.concat(transformedDocs); }, []); const sliceInfo = { allChunkNum:allChunkNum, ...res.data, doc: values1 }; delete sliceInfo.code; useChatStore.getState().updateCurrentSession((session) => { session.messages = session.messages.map((item, index) => { if (index === session.messages.length - 1 && item.role !== 'user') { return { ...item, sliceInfo: sliceInfo, }; } else { return { ...item, } } }); }); } catch (error) { console.error(error); } })(); } return finish(); } // 获取当前的数据 const currentData = info.data; const formatStart = '```think'; const formatEnd = 'think```'; if (currentData?.startsWith(formatStart)) { remainText += currentData.replace(formatStart, '```think\n'); } else if (currentData?.startsWith(formatEnd)) { remainText += currentData.replace(formatEnd, '```'); } else { remainText += currentData; } }, async onclose() { finish(); if (sliceInfoPromise) { await sliceInfoPromise; // 等待 sliceInfo 加载完成 } const session = useChatStore.getState().sessions[0]; const item = session.messages.find(item => item.role === 'user'); const dialogName = item ? item.content : '新的聊天'; const data = { id: session.id, appId: session.appId, userId: undefined, dialogName: dialogName, messages: session.messages.map(item => ({ id: item.id, date: item.date, role: item.role, content: item.content, sliceInfo: item.sliceInfo, })), }; const messages = session.messages.slice(); const backList = messages.reverse(); const record = backList.find(item => item.content && item.role === 'assistant'); if (record) { useChatStore.setState({ message: { content: record.content as string, role: record.role, } }); } const chatMode = useChatStore.getState().chatMode; if (chatMode === 'LOCAL') { await api.post('deepseek/api/dialog/save', data); } else { await api.post('bigmodel/api/dialog/save', data); } }, onerror(e) { options.onError?.(e); throw e; }, openWhenHidden: true, }); } catch (e) { options.onError?.(e as Error); } } async usage() { return { used: 0, total: 0, }; } async models(): Promise { return []; } }