Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

logic-hooks.ts 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. import { Authorization } from '@/constants/authorization';
  2. import { MessageType } from '@/constants/chat';
  3. import { LanguageTranslationMap } from '@/constants/common';
  4. import { ResponseType } from '@/interfaces/database/base';
  5. import { IAnswer, Message } from '@/interfaces/database/chat';
  6. import { IKnowledgeFile } from '@/interfaces/database/knowledge';
  7. import { IClientConversation, IMessage } from '@/pages/chat/interface';
  8. import api from '@/utils/api';
  9. import { getAuthorization } from '@/utils/authorization-util';
  10. import { buildMessageUuid, getMessagePureId } from '@/utils/chat';
  11. import { PaginationProps, message } from 'antd';
  12. import { FormInstance } from 'antd/lib';
  13. import axios from 'axios';
  14. import { EventSourceParserStream } from 'eventsource-parser/stream';
  15. import {
  16. ChangeEventHandler,
  17. useCallback,
  18. useEffect,
  19. useMemo,
  20. useRef,
  21. useState,
  22. } from 'react';
  23. import { useTranslation } from 'react-i18next';
  24. import { v4 as uuid } from 'uuid';
  25. import { useTranslate } from './common-hooks';
  26. import { useSetPaginationParams } from './route-hook';
  27. import { useFetchTenantInfo, useSaveSetting } from './user-setting-hooks';
  28. export const useSetSelectedRecord = <T = IKnowledgeFile>() => {
  29. const [currentRecord, setCurrentRecord] = useState<T>({} as T);
  30. const setRecord = (record: T) => {
  31. setCurrentRecord(record);
  32. };
  33. return { currentRecord, setRecord };
  34. };
  35. export const useHandleSearchChange = () => {
  36. const [searchString, setSearchString] = useState('');
  37. const handleInputChange = useCallback(
  38. (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
  39. const value = e.target.value;
  40. setSearchString(value);
  41. },
  42. [],
  43. );
  44. return { handleInputChange, searchString };
  45. };
  46. export const useChangeLanguage = () => {
  47. const { i18n } = useTranslation();
  48. const { saveSetting } = useSaveSetting();
  49. const changeLanguage = (lng: string) => {
  50. i18n.changeLanguage(
  51. LanguageTranslationMap[lng as keyof typeof LanguageTranslationMap],
  52. );
  53. saveSetting({ language: lng });
  54. };
  55. return changeLanguage;
  56. };
  57. export const useGetPaginationWithRouter = () => {
  58. const { t } = useTranslate('common');
  59. const {
  60. setPaginationParams,
  61. page,
  62. size: pageSize,
  63. } = useSetPaginationParams();
  64. const onPageChange: PaginationProps['onChange'] = useCallback(
  65. (pageNumber: number, pageSize: number) => {
  66. setPaginationParams(pageNumber, pageSize);
  67. },
  68. [setPaginationParams],
  69. );
  70. const setCurrentPagination = useCallback(
  71. (pagination: { page: number; pageSize?: number }) => {
  72. setPaginationParams(pagination.page, pagination.pageSize);
  73. },
  74. [setPaginationParams],
  75. );
  76. const pagination: PaginationProps = useMemo(() => {
  77. return {
  78. showQuickJumper: true,
  79. total: 0,
  80. showSizeChanger: true,
  81. current: page,
  82. pageSize: pageSize,
  83. pageSizeOptions: [1, 2, 10, 20, 50, 100],
  84. onChange: onPageChange,
  85. showTotal: (total) => `${t('total')} ${total}`,
  86. };
  87. }, [t, onPageChange, page, pageSize]);
  88. return {
  89. pagination,
  90. setPagination: setCurrentPagination,
  91. };
  92. };
  93. export const useGetPagination = () => {
  94. const [pagination, setPagination] = useState({ page: 1, pageSize: 10 });
  95. const { t } = useTranslate('common');
  96. const onPageChange: PaginationProps['onChange'] = useCallback(
  97. (pageNumber: number, pageSize: number) => {
  98. setPagination({ page: pageNumber, pageSize });
  99. },
  100. [],
  101. );
  102. const currentPagination: PaginationProps = useMemo(() => {
  103. return {
  104. showQuickJumper: true,
  105. total: 0,
  106. showSizeChanger: true,
  107. current: pagination.page,
  108. pageSize: pagination.pageSize,
  109. pageSizeOptions: [1, 2, 10, 20, 50, 100],
  110. onChange: onPageChange,
  111. showTotal: (total) => `${t('total')} ${total}`,
  112. };
  113. }, [t, onPageChange, pagination]);
  114. return {
  115. pagination: currentPagination,
  116. };
  117. };
  118. export interface AppConf {
  119. appName: string;
  120. }
  121. export const useFetchAppConf = () => {
  122. const [appConf, setAppConf] = useState<AppConf>({} as AppConf);
  123. const fetchAppConf = useCallback(async () => {
  124. const ret = await axios.get('/conf.json');
  125. setAppConf(ret.data);
  126. }, []);
  127. useEffect(() => {
  128. fetchAppConf();
  129. }, [fetchAppConf]);
  130. return appConf;
  131. };
  132. export const useSendMessageWithSse = (
  133. url: string = api.completeConversation,
  134. ) => {
  135. const [answer, setAnswer] = useState<IAnswer>({} as IAnswer);
  136. const [done, setDone] = useState(true);
  137. const timer = useRef<any>();
  138. const resetAnswer = useCallback(() => {
  139. if (timer.current) {
  140. clearTimeout(timer.current);
  141. }
  142. timer.current = setTimeout(() => {
  143. setAnswer({} as IAnswer);
  144. clearTimeout(timer.current);
  145. }, 1000);
  146. }, []);
  147. const send = useCallback(
  148. async (
  149. body: any,
  150. controller?: AbortController,
  151. ): Promise<{ response: Response; data: ResponseType } | undefined> => {
  152. try {
  153. setDone(false);
  154. const response = await fetch(url, {
  155. method: 'POST',
  156. headers: {
  157. [Authorization]: getAuthorization(),
  158. 'Content-Type': 'application/json',
  159. },
  160. body: JSON.stringify(body),
  161. signal: controller?.signal,
  162. });
  163. const res = response.clone().json();
  164. const reader = response?.body
  165. ?.pipeThrough(new TextDecoderStream())
  166. .pipeThrough(new EventSourceParserStream())
  167. .getReader();
  168. while (true) {
  169. const x = await reader?.read();
  170. if (x) {
  171. const { done, value } = x;
  172. if (done) {
  173. console.info('done');
  174. resetAnswer();
  175. break;
  176. }
  177. try {
  178. const val = JSON.parse(value?.data || '');
  179. const d = val?.data;
  180. if (typeof d !== 'boolean') {
  181. console.info('data:', d);
  182. setAnswer({
  183. ...d,
  184. conversationId: body?.conversation_id,
  185. });
  186. }
  187. } catch (e) {
  188. console.warn(e);
  189. }
  190. }
  191. }
  192. console.info('done?');
  193. setDone(true);
  194. resetAnswer();
  195. return { data: await res, response };
  196. } catch (e) {
  197. setDone(true);
  198. resetAnswer();
  199. console.warn(e);
  200. }
  201. },
  202. [url, resetAnswer],
  203. );
  204. return { send, answer, done, setDone, resetAnswer };
  205. };
  206. export const useSpeechWithSse = (url: string = api.tts) => {
  207. const read = useCallback(
  208. async (body: any) => {
  209. const response = await fetch(url, {
  210. method: 'POST',
  211. headers: {
  212. [Authorization]: getAuthorization(),
  213. 'Content-Type': 'application/json',
  214. },
  215. body: JSON.stringify(body),
  216. });
  217. try {
  218. const res = await response.clone().json();
  219. if (res?.code !== 0) {
  220. message.error(res?.message);
  221. }
  222. } catch (error) {
  223. console.warn('🚀 ~ error:', error);
  224. }
  225. return response;
  226. },
  227. [url],
  228. );
  229. return { read };
  230. };
  231. //#region chat hooks
  232. export const useScrollToBottom = (messages?: unknown) => {
  233. const ref = useRef<HTMLDivElement>(null);
  234. const scrollToBottom = useCallback(() => {
  235. if (messages) {
  236. ref.current?.scrollIntoView({ behavior: 'instant' });
  237. }
  238. }, [messages]); // If the message changes, scroll to the bottom
  239. useEffect(() => {
  240. scrollToBottom();
  241. }, [scrollToBottom]);
  242. return ref;
  243. };
  244. export const useHandleMessageInputChange = () => {
  245. const [value, setValue] = useState('');
  246. const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
  247. const value = e.target.value;
  248. const nextValue = value.replaceAll('\\n', '\n').replaceAll('\\t', '\t');
  249. setValue(nextValue);
  250. };
  251. return {
  252. handleInputChange,
  253. value,
  254. setValue,
  255. };
  256. };
  257. export const useSelectDerivedMessages = () => {
  258. const [derivedMessages, setDerivedMessages] = useState<IMessage[]>([]);
  259. const ref = useScrollToBottom(derivedMessages);
  260. const addNewestQuestion = useCallback(
  261. (message: Message, answer: string = '') => {
  262. setDerivedMessages((pre) => {
  263. return [
  264. ...pre,
  265. {
  266. ...message,
  267. id: buildMessageUuid(message),
  268. },
  269. {
  270. role: MessageType.Assistant,
  271. content: answer,
  272. id: buildMessageUuid({ ...message, role: MessageType.Assistant }),
  273. },
  274. ];
  275. });
  276. },
  277. [],
  278. );
  279. // Add the streaming message to the last item in the message list
  280. const addNewestAnswer = useCallback((answer: IAnswer) => {
  281. setDerivedMessages((pre) => {
  282. return [
  283. ...(pre?.slice(0, -1) ?? []),
  284. {
  285. role: MessageType.Assistant,
  286. content: answer.answer,
  287. reference: answer.reference,
  288. id: buildMessageUuid({
  289. id: answer.id,
  290. role: MessageType.Assistant,
  291. }),
  292. prompt: answer.prompt,
  293. audio_binary: answer.audio_binary,
  294. },
  295. ];
  296. });
  297. }, []);
  298. const removeLatestMessage = useCallback(() => {
  299. setDerivedMessages((pre) => {
  300. const nextMessages = pre?.slice(0, -2) ?? [];
  301. return nextMessages;
  302. });
  303. }, []);
  304. const removeMessageById = useCallback(
  305. (messageId: string) => {
  306. setDerivedMessages((pre) => {
  307. const nextMessages =
  308. pre?.filter(
  309. (x) => getMessagePureId(x.id) !== getMessagePureId(messageId),
  310. ) ?? [];
  311. return nextMessages;
  312. });
  313. },
  314. [setDerivedMessages],
  315. );
  316. const removeMessagesAfterCurrentMessage = useCallback(
  317. (messageId: string) => {
  318. setDerivedMessages((pre) => {
  319. const index = pre.findIndex((x) => x.id === messageId);
  320. if (index !== -1) {
  321. let nextMessages = pre.slice(0, index + 2) ?? [];
  322. const latestMessage = nextMessages.at(-1);
  323. nextMessages = latestMessage
  324. ? [
  325. ...nextMessages.slice(0, -1),
  326. {
  327. ...latestMessage,
  328. content: '',
  329. reference: undefined,
  330. prompt: undefined,
  331. },
  332. ]
  333. : nextMessages;
  334. return nextMessages;
  335. }
  336. return pre;
  337. });
  338. },
  339. [setDerivedMessages],
  340. );
  341. return {
  342. ref,
  343. derivedMessages,
  344. setDerivedMessages,
  345. addNewestQuestion,
  346. addNewestAnswer,
  347. removeLatestMessage,
  348. removeMessageById,
  349. removeMessagesAfterCurrentMessage,
  350. };
  351. };
  352. export interface IRemoveMessageById {
  353. removeMessageById(messageId: string): void;
  354. }
  355. export const useRemoveMessageById = (
  356. setCurrentConversation: (
  357. callback: (state: IClientConversation) => IClientConversation,
  358. ) => void,
  359. ) => {
  360. const removeMessageById = useCallback(
  361. (messageId: string) => {
  362. setCurrentConversation((pre) => {
  363. const nextMessages =
  364. pre.message?.filter(
  365. (x) => getMessagePureId(x.id) !== getMessagePureId(messageId),
  366. ) ?? [];
  367. return {
  368. ...pre,
  369. message: nextMessages,
  370. };
  371. });
  372. },
  373. [setCurrentConversation],
  374. );
  375. return { removeMessageById };
  376. };
  377. export const useRemoveMessagesAfterCurrentMessage = (
  378. setCurrentConversation: (
  379. callback: (state: IClientConversation) => IClientConversation,
  380. ) => void,
  381. ) => {
  382. const removeMessagesAfterCurrentMessage = useCallback(
  383. (messageId: string) => {
  384. setCurrentConversation((pre) => {
  385. const index = pre.message?.findIndex((x) => x.id === messageId);
  386. if (index !== -1) {
  387. let nextMessages = pre.message?.slice(0, index + 2) ?? [];
  388. const latestMessage = nextMessages.at(-1);
  389. nextMessages = latestMessage
  390. ? [
  391. ...nextMessages.slice(0, -1),
  392. {
  393. ...latestMessage,
  394. content: '',
  395. reference: undefined,
  396. prompt: undefined,
  397. },
  398. ]
  399. : nextMessages;
  400. return {
  401. ...pre,
  402. message: nextMessages,
  403. };
  404. }
  405. return pre;
  406. });
  407. },
  408. [setCurrentConversation],
  409. );
  410. return { removeMessagesAfterCurrentMessage };
  411. };
  412. export interface IRegenerateMessage {
  413. regenerateMessage?: (message: Message) => void;
  414. }
  415. export const useRegenerateMessage = ({
  416. removeMessagesAfterCurrentMessage,
  417. sendMessage,
  418. messages,
  419. }: {
  420. removeMessagesAfterCurrentMessage(messageId: string): void;
  421. sendMessage({
  422. message,
  423. }: {
  424. message: Message;
  425. messages?: Message[];
  426. }): void | Promise<any>;
  427. messages: Message[];
  428. }) => {
  429. const regenerateMessage = useCallback(
  430. async (message: Message) => {
  431. if (message.id) {
  432. removeMessagesAfterCurrentMessage(message.id);
  433. const index = messages.findIndex((x) => x.id === message.id);
  434. let nextMessages;
  435. if (index !== -1) {
  436. nextMessages = messages.slice(0, index);
  437. }
  438. sendMessage({
  439. message: { ...message, id: uuid() },
  440. messages: nextMessages,
  441. });
  442. }
  443. },
  444. [removeMessagesAfterCurrentMessage, sendMessage, messages],
  445. );
  446. return { regenerateMessage };
  447. };
  448. // #endregion
  449. /**
  450. *
  451. * @param defaultId
  452. * used to switch between different items, similar to radio
  453. * @returns
  454. */
  455. export const useSelectItem = (defaultId?: string) => {
  456. const [selectedId, setSelectedId] = useState('');
  457. const handleItemClick = useCallback(
  458. (id: string) => () => {
  459. setSelectedId(id);
  460. },
  461. [],
  462. );
  463. useEffect(() => {
  464. if (defaultId) {
  465. setSelectedId(defaultId);
  466. }
  467. }, [defaultId]);
  468. return { selectedId, handleItemClick };
  469. };
  470. export const useFetchModelId = () => {
  471. const { data: tenantInfo } = useFetchTenantInfo();
  472. return tenantInfo?.llm_id ?? '';
  473. };
  474. const ChunkTokenNumMap = {
  475. naive: 128,
  476. knowledge_graph: 8192,
  477. };
  478. export const useHandleChunkMethodSelectChange = (form: FormInstance) => {
  479. // const form = Form.useFormInstance();
  480. const handleChange = useCallback(
  481. (value: string) => {
  482. if (value in ChunkTokenNumMap) {
  483. form.setFieldValue(
  484. ['parser_config', 'chunk_token_num'],
  485. ChunkTokenNumMap[value as keyof typeof ChunkTokenNumMap],
  486. );
  487. }
  488. },
  489. [form],
  490. );
  491. return handleChange;
  492. };