bigModel.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. "use client";
  2. import { REQUEST_TIMEOUT_MS } from "@/app/constant";
  3. import { useChatStore } from "@/app/store";
  4. import {
  5. ChatOptions,
  6. LLMApi,
  7. LLMModel,
  8. } from "../api";
  9. import Locale from "../../locales";
  10. import {
  11. EventStreamContentType,
  12. fetchEventSource,
  13. } from "@fortaine/fetch-event-source";
  14. import { prettyObject } from "@/app/utils/format";
  15. import { getMessageTextContent } from "@/app/utils";
  16. import api from "@/app/api/api";
  17. import {processSliceData} from "@/app/utils/index";
  18. export class BigModelApi implements LLMApi {
  19. public baseURL: string;
  20. public apiPath: string;
  21. constructor() {
  22. const chatMode = useChatStore.getState().chatMode;
  23. // this.baseURL = 'http://xia0miduo.gicp.net:8401';
  24. this.baseURL = '/bigmodel-api';
  25. this.apiPath = this.baseURL + '/deepseek/api/chat';
  26. }
  27. async chat(options: ChatOptions) {
  28. const messages = options.messages.map((item) => {
  29. return {
  30. role: item.role,
  31. content: getMessageTextContent(item),
  32. }
  33. });
  34. const userMessages = messages.filter(item => item.content);
  35. if (userMessages.length % 2 === 0) {
  36. userMessages.unshift({
  37. role: "user",
  38. content: "⠀",
  39. });
  40. }
  41. // 参数
  42. const params = {
  43. appId: options.config.appId,// 应用id
  44. prompt: userMessages,
  45. // 进阶配置
  46. request_id: 'jkec2024-knowledge-base',
  47. returnType: undefined,
  48. knowledge_ids: undefined,
  49. document_ids: undefined,
  50. };
  51. const controller = new AbortController();
  52. options.onController?.(controller);
  53. try {
  54. const userInfo = localStorage.getItem('userInfo');
  55. const token = userInfo ? JSON.parse(userInfo).token : "";
  56. const chatPath = this.apiPath;
  57. const paramsflag = { ...params }
  58. paramsflag.prompt.forEach(val => {
  59. val.content = btoa(unescape(encodeURIComponent(val.content)))
  60. });
  61. const chatPayload = {
  62. method: "POST",
  63. body: JSON.stringify(paramsflag),
  64. // body: btoa(unescape(encodeURIComponent(JSON.stringify(params)))),
  65. signal: controller.signal,
  66. headers: {
  67. 'Content-Type': 'application/json',
  68. 'Authorization': 'Bearer ' + token,
  69. 'clientid': 'e5cd7e4891bf95d1d19206ce24a7b32e'
  70. },
  71. };
  72. const requestTimeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
  73. let responseText = "";
  74. let remainText = "";
  75. let finished = false;
  76. let finishData: any = {}; //保存后数据
  77. function animateResponseText() {
  78. if (finished || controller.signal.aborted) {
  79. responseText += remainText;
  80. if (responseText?.length === 0) {
  81. options.onError?.(new Error("请求已中止,请检查网络环境。"));
  82. }
  83. return;
  84. }
  85. if (remainText.length > 0) {
  86. const fetchCount = Math.max(1, Math.round(remainText.length / 60));
  87. const fetchText = remainText.slice(0, fetchCount);
  88. responseText += fetchText;
  89. remainText = remainText.slice(fetchCount);
  90. options.onUpdate?.(responseText, fetchText);
  91. }
  92. requestAnimationFrame(animateResponseText);
  93. }
  94. animateResponseText();
  95. const finish = () => {
  96. if (!finished) {
  97. finished = true;
  98. options.onFinish(responseText + remainText);
  99. }
  100. };
  101. controller.signal.onabort = finish;
  102. let sliceInfoPromise: Promise<void> | null = null;
  103. fetchEventSource(chatPath, {
  104. ...chatPayload,
  105. async onopen(res: any) {
  106. clearTimeout(requestTimeoutId);
  107. const contentType = res.headers.get("content-type");
  108. if (contentType?.startsWith("text/plain")) {
  109. responseText = await res.clone().text();
  110. return finish();
  111. }
  112. if (
  113. !res.ok ||
  114. !res.headers.get("content-type")?.startsWith(EventStreamContentType) ||
  115. res.status !== 200
  116. ) {
  117. const responseTexts = [responseText];
  118. let extraInfo = await res.clone().text();
  119. try {
  120. const resJson = await res.clone().json();
  121. extraInfo = prettyObject(resJson);
  122. } catch { }
  123. if (res.status === 401) {
  124. responseTexts.push(Locale.Error.Unauthorized);
  125. }
  126. if (extraInfo) {
  127. responseTexts.push(extraInfo);
  128. }
  129. responseText = responseTexts.join("\n\n");
  130. return finish();
  131. }
  132. },
  133. onmessage: async (msg) => {
  134. const info = JSON.parse(msg.data);
  135. if (info.event === 'finish') { // 完成
  136. const chatMode = useChatStore.getState().chatMode;
  137. if (info.data) {
  138. // console.log('info.data---', info.data);
  139. finishData = info && info?.data ? JSON.parse(info.data) : {};
  140. }
  141. // console.log('finishData', finishData);
  142. if (chatMode === 'LOCAL') {// 切片
  143. useChatStore.getState().updateCurrentSession((se) => {
  144. se.chat_id = info.id;
  145. });
  146. sliceInfoPromise = (async () => {
  147. try {
  148. const res: any = await api.get(`deepseek/api/slice/search/${info.id}`);
  149. let allChunkNum = 0;
  150. delete res.data.code;
  151. const values1 = Object.keys(res.data).reduce((acc, knowledge_id) => {
  152. const docs = res.data[knowledge_id];
  153. const transformedDocs = docs.map((doc: any) => {
  154. allChunkNum += doc.chunk_nums;
  155. return {
  156. knowledge_id,
  157. ...doc
  158. }
  159. });
  160. return acc.concat(transformedDocs);
  161. }, []);
  162. const result = processSliceData(values1);
  163. // 使用解构赋值,让结果更清晰
  164. const { withDeprecated, withoutDeprecated } = result;
  165. const sliceInfo = {
  166. allChunkNum: allChunkNum,
  167. ...res.data,
  168. doc: values1,
  169. docDeprecated:withDeprecated,
  170. docActive:withoutDeprecated,
  171. };
  172. // console.log('values1---', values1,withDeprecated,withoutDeprecated);
  173. // console.log('sliceInfo---', sliceInfo);
  174. delete sliceInfo.code;
  175. useChatStore.getState().updateCurrentSession((session) => {
  176. session.messages = session.messages.map((item, index) => {
  177. if (index === session.messages.length - 1 && item.role !== 'user') {
  178. return {
  179. ...item,
  180. sliceInfo: sliceInfo,
  181. delSliceInfo: withDeprecated ? sliceInfo : []
  182. };
  183. } else {
  184. return {
  185. ...item,
  186. }
  187. }
  188. });
  189. });
  190. } catch (error) {
  191. console.error(error);
  192. }
  193. })();
  194. }
  195. return finish();
  196. }
  197. // 获取当前的数据
  198. const currentData = info.data;
  199. const formatStart = '```think';
  200. const formatEnd = 'think```';
  201. if (currentData?.startsWith(formatStart)) {
  202. remainText += currentData.replace(formatStart, '```think\n');
  203. } else if (currentData?.startsWith(formatEnd)) {
  204. remainText += currentData.replace(formatEnd, '```');
  205. } else {
  206. remainText += currentData;
  207. }
  208. },
  209. async onclose() {
  210. finish();
  211. if (sliceInfoPromise) {
  212. await sliceInfoPromise; // 等待 sliceInfo 加载完成
  213. }
  214. const session = useChatStore.getState().sessions[0];
  215. const item = session.messages.find(item => item.role === 'user');
  216. const dialogName = item ? item.content : '新的聊天';
  217. const data = {
  218. id: session.id,
  219. appId: session.appId,
  220. userId: undefined,
  221. dialogName: dialogName,
  222. messages: session.messages.map((item: any) => ({
  223. id: item.id,
  224. date: item.date,
  225. role: item.role,
  226. content: btoa(unescape(encodeURIComponent(item.content))),
  227. sliceInfo: item.sliceInfo,
  228. ...finishData
  229. })),
  230. };
  231. const messages = session.messages.slice();
  232. const backList = messages.reverse();
  233. const record = backList.find(item => item.content && item.role === 'assistant');
  234. if (record) {
  235. useChatStore.setState({
  236. message: {
  237. content: record.content as string,
  238. role: record.role,
  239. }
  240. });
  241. }
  242. const chatMode = useChatStore.getState().chatMode;
  243. // console.log('保存对话数据----', data);
  244. // if (chatMode === 'LOCAL') {
  245. await api.post('deepseek/api/dialog/save', data);
  246. // } else {
  247. // await api.post('bigmodel/api/dialog/save', data);
  248. // }
  249. },
  250. onerror(e) {
  251. options.onError?.(e);
  252. throw e;
  253. },
  254. openWhenHidden: true,
  255. });
  256. } catch (e) {
  257. options.onError?.(e as Error);
  258. }
  259. }
  260. async usage() {
  261. return {
  262. used: 0,
  263. total: 0,
  264. };
  265. }
  266. async models(): Promise<LLMModel[]> {
  267. return [];
  268. }
  269. }