balibabu commited on
Commit
e68f488
·
1 Parent(s): 0bf0841

feat: Click on the relevant question tag to continue searching for answers #2247 (#2320)

Browse files

### What problem does this PR solve?

feat: Click on the relevant question tag to continue searching for
answers #2247

### Type of change


- [x] New Feature (non-breaking change which adds functionality)

web/src/hooks/logic-hooks.ts CHANGED
@@ -217,6 +217,10 @@ export const useSendMessageWithSse = (
217
  const [answer, setAnswer] = useState<IAnswer>({} as IAnswer);
218
  const [done, setDone] = useState(true);
219
 
 
 
 
 
220
  const send = useCallback(
221
  async (
222
  body: any,
@@ -251,7 +255,7 @@ export const useSendMessageWithSse = (
251
  const val = JSON.parse(value?.data || '');
252
  const d = val?.data;
253
  if (typeof d !== 'boolean') {
254
- // console.info('data:', d);
255
  setAnswer({
256
  ...d,
257
  conversationId: body?.conversation_id,
@@ -264,18 +268,16 @@ export const useSendMessageWithSse = (
264
  }
265
  console.info('done?');
266
  setDone(true);
267
- setAnswer({} as IAnswer);
268
  return { data: await res, response };
269
  } catch (e) {
270
  setDone(true);
271
- setAnswer({} as IAnswer);
272
  console.warn(e);
273
  }
274
  },
275
  [url],
276
  );
277
 
278
- return { send, answer, done, setDone };
279
  };
280
 
281
  export const useSpeechWithSse = (url: string = api.tts) => {
 
217
  const [answer, setAnswer] = useState<IAnswer>({} as IAnswer);
218
  const [done, setDone] = useState(true);
219
 
220
+ const resetAnswer = useCallback(() => {
221
+ setAnswer({} as IAnswer);
222
+ }, []);
223
+
224
  const send = useCallback(
225
  async (
226
  body: any,
 
255
  const val = JSON.parse(value?.data || '');
256
  const d = val?.data;
257
  if (typeof d !== 'boolean') {
258
+ console.info('data:', d);
259
  setAnswer({
260
  ...d,
261
  conversationId: body?.conversation_id,
 
268
  }
269
  console.info('done?');
270
  setDone(true);
 
271
  return { data: await res, response };
272
  } catch (e) {
273
  setDone(true);
 
274
  console.warn(e);
275
  }
276
  },
277
  [url],
278
  );
279
 
280
+ return { send, answer, done, setDone, resetAnswer };
281
  };
282
 
283
  export const useSpeechWithSse = (url: string = api.tts) => {
web/src/layouts/components/header/index.tsx CHANGED
@@ -2,14 +2,14 @@ import { ReactComponent as FileIcon } from '@/assets/svg/file-management.svg';
2
  import { ReactComponent as GraphIcon } from '@/assets/svg/graph.svg';
3
  import { ReactComponent as KnowledgeBaseIcon } from '@/assets/svg/knowledge-base.svg';
4
  import { useTranslate } from '@/hooks/common-hooks';
 
5
  import { useNavigateWithFromState } from '@/hooks/route-hook';
 
6
  import { Flex, Layout, Radio, Space, theme } from 'antd';
7
  import { useCallback, useMemo } from 'react';
8
  import { useLocation } from 'umi';
9
  import Toolbar from '../right-toolbar';
10
 
11
- import { useFetchAppConf } from '@/hooks/logic-hooks';
12
- import { MessageOutlined } from '@ant-design/icons';
13
  import styles from './index.less';
14
 
15
  const { Header } = Layout;
@@ -27,7 +27,7 @@ const RagHeader = () => {
27
  () => [
28
  { path: '/knowledge', name: t('knowledgeBase'), icon: KnowledgeBaseIcon },
29
  { path: '/chat', name: t('chat'), icon: MessageOutlined },
30
- // { path: '/search', name: t('search'), icon: SearchOutlined },
31
  { path: '/flow', name: t('flow'), icon: GraphIcon },
32
  { path: '/file', name: t('fileManager'), icon: FileIcon },
33
  ],
 
2
  import { ReactComponent as GraphIcon } from '@/assets/svg/graph.svg';
3
  import { ReactComponent as KnowledgeBaseIcon } from '@/assets/svg/knowledge-base.svg';
4
  import { useTranslate } from '@/hooks/common-hooks';
5
+ import { useFetchAppConf } from '@/hooks/logic-hooks';
6
  import { useNavigateWithFromState } from '@/hooks/route-hook';
7
+ import { MessageOutlined, SearchOutlined } from '@ant-design/icons';
8
  import { Flex, Layout, Radio, Space, theme } from 'antd';
9
  import { useCallback, useMemo } from 'react';
10
  import { useLocation } from 'umi';
11
  import Toolbar from '../right-toolbar';
12
 
 
 
13
  import styles from './index.less';
14
 
15
  const { Header } = Layout;
 
27
  () => [
28
  { path: '/knowledge', name: t('knowledgeBase'), icon: KnowledgeBaseIcon },
29
  { path: '/chat', name: t('chat'), icon: MessageOutlined },
30
+ { path: '/search', name: t('search'), icon: SearchOutlined },
31
  { path: '/flow', name: t('flow'), icon: GraphIcon },
32
  { path: '/file', name: t('fileManager'), icon: FileIcon },
33
  ],
web/src/pages/chat/hooks.ts CHANGED
@@ -19,21 +19,12 @@ import {
19
  } from '@/hooks/common-hooks';
20
  import {
21
  useRegenerateMessage,
22
- useRemoveMessageById,
23
- useRemoveMessagesAfterCurrentMessage,
24
- useScrollToBottom,
25
  useSelectDerivedMessages,
26
  useSendMessageWithSse,
27
  } from '@/hooks/logic-hooks';
28
- import {
29
- IAnswer,
30
- IConversation,
31
- IDialog,
32
- Message,
33
- } from '@/interfaces/database/chat';
34
  import { IChunk } from '@/interfaces/database/knowledge';
35
  import { getFileExtension } from '@/utils';
36
- import { buildMessageUuid } from '@/utils/chat';
37
  import { useMutationState } from '@tanstack/react-query';
38
  import { get } from 'lodash';
39
  import trim from 'lodash/trim';
@@ -251,118 +242,6 @@ export const useSetConversation = () => {
251
  return { setConversation };
252
  };
253
 
254
- export const useSelectCurrentConversation = () => {
255
- const [currentConversation, setCurrentConversation] =
256
- useState<IClientConversation>({} as IClientConversation);
257
- const { data: conversation, loading } = useFetchNextConversation();
258
- const { data: dialog } = useFetchNextDialog();
259
- const { conversationId, dialogId } = useGetChatSearchParams();
260
- const { removeMessageById } = useRemoveMessageById(setCurrentConversation);
261
- const { removeMessagesAfterCurrentMessage } =
262
- useRemoveMessagesAfterCurrentMessage(setCurrentConversation);
263
-
264
- // Show the entered message in the conversation immediately after sending the message
265
- const addNewestConversation = useCallback(
266
- (message: Message, answer: string = '') => {
267
- setCurrentConversation((pre) => {
268
- return {
269
- ...pre,
270
- message: [
271
- ...pre.message,
272
- {
273
- ...message,
274
- id: buildMessageUuid(message),
275
- } as IMessage,
276
- {
277
- role: MessageType.Assistant,
278
- content: answer,
279
- id: buildMessageUuid({ ...message, role: MessageType.Assistant }),
280
- reference: {},
281
- } as IMessage,
282
- ],
283
- };
284
- });
285
- },
286
- [],
287
- );
288
-
289
- // Add the streaming message to the last item in the message list
290
- const addNewestAnswer = useCallback((answer: IAnswer) => {
291
- setCurrentConversation((pre) => {
292
- const latestMessage = pre.message?.at(-1);
293
-
294
- if (latestMessage) {
295
- return {
296
- ...pre,
297
- message: [
298
- ...pre.message.slice(0, -1),
299
- {
300
- ...latestMessage,
301
- content: answer.answer,
302
- reference: answer.reference,
303
- id: buildMessageUuid({
304
- id: answer.id,
305
- role: MessageType.Assistant,
306
- }),
307
- prompt: answer.prompt,
308
- } as IMessage,
309
- ],
310
- };
311
- }
312
- return pre;
313
- });
314
- }, []);
315
-
316
- const removeLatestMessage = useCallback(() => {
317
- setCurrentConversation((pre) => {
318
- const nextMessages = pre.message?.slice(0, -2) ?? [];
319
- return {
320
- ...pre,
321
- message: nextMessages,
322
- };
323
- });
324
- }, []);
325
-
326
- const addPrologue = useCallback(() => {
327
- if (dialogId !== '' && conversationId === '') {
328
- const prologue = dialog.prompt_config?.prologue;
329
-
330
- const nextMessage = {
331
- role: MessageType.Assistant,
332
- content: prologue,
333
- id: uuid(),
334
- } as IMessage;
335
-
336
- setCurrentConversation({
337
- id: '',
338
- dialog_id: dialogId,
339
- reference: [],
340
- message: [nextMessage],
341
- } as any);
342
- }
343
- }, [conversationId, dialog, dialogId]);
344
-
345
- useEffect(() => {
346
- addPrologue();
347
- }, [addPrologue]);
348
-
349
- useEffect(() => {
350
- if (conversationId) {
351
- setCurrentConversation(conversation);
352
- }
353
- }, [conversation, conversationId]);
354
-
355
- return {
356
- currentConversation,
357
- addNewestConversation,
358
- removeLatestMessage,
359
- addNewestAnswer,
360
- removeMessageById,
361
- removeMessagesAfterCurrentMessage,
362
- loading,
363
- };
364
- };
365
-
366
  // export const useScrollToBottom = (currentConversation: IClientConversation) => {
367
  // const ref = useRef<HTMLDivElement>(null);
368
 
@@ -430,32 +309,6 @@ export const useSelectNextMessages = () => {
430
  };
431
  };
432
 
433
- export const useFetchConversationOnMount = () => {
434
- const { conversationId } = useGetChatSearchParams();
435
- const {
436
- currentConversation,
437
- addNewestConversation,
438
- removeLatestMessage,
439
- addNewestAnswer,
440
- loading,
441
- removeMessageById,
442
- removeMessagesAfterCurrentMessage,
443
- } = useSelectCurrentConversation();
444
- const ref = useScrollToBottom(currentConversation);
445
-
446
- return {
447
- currentConversation,
448
- addNewestConversation,
449
- ref,
450
- removeLatestMessage,
451
- addNewestAnswer,
452
- conversationId,
453
- loading,
454
- removeMessageById,
455
- removeMessagesAfterCurrentMessage,
456
- };
457
- };
458
-
459
  export const useHandleMessageInputChange = () => {
460
  const [value, setValue] = useState('');
461
 
@@ -477,7 +330,7 @@ export const useSendNextMessage = () => {
477
  const { conversationId } = useGetChatSearchParams();
478
  const { handleInputChange, value, setValue } = useHandleMessageInputChange();
479
  const { handleClickConversation } = useClickConversationCard();
480
- const { send, answer, done, setDone } = useSendMessageWithSse();
481
  const {
482
  ref,
483
  derivedMessages,
@@ -557,10 +410,11 @@ export const useSendNextMessage = () => {
557
 
558
  useEffect(() => {
559
  // #1289
 
560
  if (
561
  answer.answer &&
562
- !done &&
563
- (answer?.conversationId === conversationId || conversationId === '')
564
  ) {
565
  addNewestAnswer(answer);
566
  }
@@ -570,8 +424,10 @@ export const useSendNextMessage = () => {
570
  // #1289 switch to another conversion window when the last conversion answer doesn't finish.
571
  if (conversationId) {
572
  setDone(true);
 
 
573
  }
574
- }, [setDone, conversationId]);
575
 
576
  const handlePressEnter = useCallback(
577
  (documentIds: string[]) => {
 
19
  } from '@/hooks/common-hooks';
20
  import {
21
  useRegenerateMessage,
 
 
 
22
  useSelectDerivedMessages,
23
  useSendMessageWithSse,
24
  } from '@/hooks/logic-hooks';
25
+ import { IConversation, IDialog, Message } from '@/interfaces/database/chat';
 
 
 
 
 
26
  import { IChunk } from '@/interfaces/database/knowledge';
27
  import { getFileExtension } from '@/utils';
 
28
  import { useMutationState } from '@tanstack/react-query';
29
  import { get } from 'lodash';
30
  import trim from 'lodash/trim';
 
242
  return { setConversation };
243
  };
244
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  // export const useScrollToBottom = (currentConversation: IClientConversation) => {
246
  // const ref = useRef<HTMLDivElement>(null);
247
 
 
309
  };
310
  };
311
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  export const useHandleMessageInputChange = () => {
313
  const [value, setValue] = useState('');
314
 
 
330
  const { conversationId } = useGetChatSearchParams();
331
  const { handleInputChange, value, setValue } = useHandleMessageInputChange();
332
  const { handleClickConversation } = useClickConversationCard();
333
+ const { send, answer, done, setDone, resetAnswer } = useSendMessageWithSse();
334
  const {
335
  ref,
336
  derivedMessages,
 
410
 
411
  useEffect(() => {
412
  // #1289
413
+ console.log('🚀 ~ useEffect ~ answer:', answer, done);
414
  if (
415
  answer.answer &&
416
+ (answer?.conversationId === conversationId ||
417
+ (!done && conversationId === ''))
418
  ) {
419
  addNewestAnswer(answer);
420
  }
 
424
  // #1289 switch to another conversion window when the last conversion answer doesn't finish.
425
  if (conversationId) {
426
  setDone(true);
427
+ } else {
428
+ resetAnswer();
429
  }
430
+ }, [setDone, conversationId, resetAnswer]);
431
 
432
  const handlePressEnter = useCallback(
433
  (documentIds: string[]) => {
web/src/pages/search/hooks.ts CHANGED
@@ -4,7 +4,7 @@ import { useSendMessageWithSse } from '@/hooks/logic-hooks';
4
  import { IAnswer } from '@/interfaces/database/chat';
5
  import api from '@/utils/api';
6
  import { isEmpty } from 'lodash';
7
- import { useCallback, useEffect, useState } from 'react';
8
 
9
  export const useSendQuestion = (kbIds: string[]) => {
10
  const { send, answer, done } = useSendMessageWithSse(api.ask);
@@ -18,6 +18,7 @@ export const useSendQuestion = (kbIds: string[]) => {
18
  data: mindMap,
19
  loading: mindMapLoading,
20
  } = useFetchMindMap();
 
21
 
22
  const sendQuestion = useCallback(
23
  (question: string) => {
@@ -34,10 +35,26 @@ export const useSendQuestion = (kbIds: string[]) => {
34
  [send, testChunk, kbIds, fetchRelatedQuestions, fetchMindMap],
35
  );
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  useEffect(() => {
38
  if (!isEmpty(answer)) {
39
  setCurrentAnswer(answer);
40
  }
 
 
 
41
  }, [answer]);
42
 
43
  useEffect(() => {
@@ -54,5 +71,8 @@ export const useSendQuestion = (kbIds: string[]) => {
54
  relatedQuestions: relatedQuestions?.slice(0, 5) ?? [],
55
  mindMap,
56
  mindMapLoading,
 
 
 
57
  };
58
  };
 
4
  import { IAnswer } from '@/interfaces/database/chat';
5
  import api from '@/utils/api';
6
  import { isEmpty } from 'lodash';
7
+ import { ChangeEventHandler, useCallback, useEffect, useState } from 'react';
8
 
9
  export const useSendQuestion = (kbIds: string[]) => {
10
  const { send, answer, done } = useSendMessageWithSse(api.ask);
 
18
  data: mindMap,
19
  loading: mindMapLoading,
20
  } = useFetchMindMap();
21
+ const [searchStr, setSearchStr] = useState<string>('');
22
 
23
  const sendQuestion = useCallback(
24
  (question: string) => {
 
35
  [send, testChunk, kbIds, fetchRelatedQuestions, fetchMindMap],
36
  );
37
 
38
+ const handleSearchStrChange: ChangeEventHandler<HTMLInputElement> =
39
+ useCallback((e) => {
40
+ setSearchStr(e.target.value);
41
+ }, []);
42
+
43
+ const handleClickRelatedQuestion = useCallback(
44
+ (question: string) => () => {
45
+ setSearchStr(question);
46
+ sendQuestion(question);
47
+ },
48
+ [sendQuestion],
49
+ );
50
+
51
  useEffect(() => {
52
  if (!isEmpty(answer)) {
53
  setCurrentAnswer(answer);
54
  }
55
+ return () => {
56
+ setCurrentAnswer({} as IAnswer);
57
+ };
58
  }, [answer]);
59
 
60
  useEffect(() => {
 
71
  relatedQuestions: relatedQuestions?.slice(0, 5) ?? [],
72
  mindMap,
73
  mindMapLoading,
74
+ handleClickRelatedQuestion,
75
+ searchStr,
76
+ handleSearchStrChange,
77
  };
78
  };
web/src/pages/search/index.less CHANGED
@@ -1,10 +1,17 @@
1
  .searchPage {
2
  .card {
3
  width: 100%;
 
 
 
 
 
 
4
  }
5
  .tag {
6
  padding: 4px 8px;
7
  font-size: 14px;
 
8
  }
9
  }
10
 
@@ -56,3 +63,12 @@
56
  padding-right: 10px;
57
  }
58
  }
 
 
 
 
 
 
 
 
 
 
1
  .searchPage {
2
  .card {
3
  width: 100%;
4
+ :global(.ant-card-body) {
5
+ padding: 14px;
6
+ }
7
+ p {
8
+ margin: 0;
9
+ }
10
  }
11
  .tag {
12
  padding: 4px 8px;
13
  font-size: 14px;
14
+ cursor: pointer;
15
  }
16
  }
17
 
 
63
  padding-right: 10px;
64
  }
65
  }
66
+ .answerWrapper {
67
+ background-color: #e6f4ff;
68
+ padding: 14px;
69
+ margin-top: 16px;
70
+ border-radius: 8px;
71
+ & > p {
72
+ margin: 0;
73
+ }
74
+ }
web/src/pages/search/index.tsx CHANGED
@@ -19,11 +19,14 @@ const SearchPage = () => {
19
  const list = useSelectTestingResult();
20
  const {
21
  sendQuestion,
 
 
22
  answer,
23
  sendingLoading,
24
  relatedQuestions,
25
  mindMap,
26
  mindMapLoading,
 
27
  } = useSendQuestion(checkedList);
28
 
29
  return (
@@ -37,18 +40,24 @@ const SearchPage = () => {
37
  <Flex className={styles.content}>
38
  <section className={styles.main}>
39
  <Search
 
 
40
  placeholder="input search text"
41
  onSearch={sendQuestion}
42
  size="large"
43
  loading={sendingLoading}
44
  disabled={checkedList.length === 0}
45
  />
46
- <MarkdownContent
47
- loading={sendingLoading}
48
- content={answer.answer}
49
- reference={answer.reference ?? ({} as IReference)}
50
- clickDocumentButton={() => {}}
51
- ></MarkdownContent>
 
 
 
 
52
  <List
53
  dataSource={list.chunks}
54
  renderItem={(item) => (
@@ -68,7 +77,11 @@ const SearchPage = () => {
68
  <Card>
69
  <Flex wrap="wrap" gap={'10px 0'}>
70
  {relatedQuestions?.map((x, idx) => (
71
- <Tag key={idx} className={styles.tag}>
 
 
 
 
72
  {x}
73
  </Tag>
74
  ))}
 
19
  const list = useSelectTestingResult();
20
  const {
21
  sendQuestion,
22
+ handleClickRelatedQuestion,
23
+ handleSearchStrChange,
24
  answer,
25
  sendingLoading,
26
  relatedQuestions,
27
  mindMap,
28
  mindMapLoading,
29
+ searchStr,
30
  } = useSendQuestion(checkedList);
31
 
32
  return (
 
40
  <Flex className={styles.content}>
41
  <section className={styles.main}>
42
  <Search
43
+ value={searchStr}
44
+ onChange={handleSearchStrChange}
45
  placeholder="input search text"
46
  onSearch={sendQuestion}
47
  size="large"
48
  loading={sendingLoading}
49
  disabled={checkedList.length === 0}
50
  />
51
+ {answer.answer && (
52
+ <div className={styles.answerWrapper}>
53
+ <MarkdownContent
54
+ loading={sendingLoading}
55
+ content={answer.answer}
56
+ reference={answer.reference ?? ({} as IReference)}
57
+ clickDocumentButton={() => {}}
58
+ ></MarkdownContent>
59
+ </div>
60
+ )}
61
  <List
62
  dataSource={list.chunks}
63
  renderItem={(item) => (
 
77
  <Card>
78
  <Flex wrap="wrap" gap={'10px 0'}>
79
  {relatedQuestions?.map((x, idx) => (
80
+ <Tag
81
+ key={idx}
82
+ className={styles.tag}
83
+ onClick={handleClickRelatedQuestion(x)}
84
+ >
85
  {x}
86
  </Tag>
87
  ))}