balibabu commited on
Commit
058cd84
·
1 Parent(s): cdba7f7

feat: fetch conversation and delete chat dialog (#69)

Browse files

* feat: set chat configuration to backend

* feat: exclude unEnabled variables

* feat: delete chat dialog

* feat: fetch conversation

web/src/constants/chat.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ export enum MessageType {
2
+ Assistant = 'assistant',
3
+ User = 'user',
4
+ }
web/src/hooks/commonHooks.ts ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import isEqual from 'lodash/isEqual';
2
+ import { useEffect, useRef, useState } from 'react';
3
+
4
+ export const useSetModalState = () => {
5
+ const [visible, setVisible] = useState(false);
6
+
7
+ const showModal = () => {
8
+ setVisible(true);
9
+ };
10
+ const hideModal = () => {
11
+ setVisible(false);
12
+ };
13
+
14
+ return { visible, showModal, hideModal };
15
+ };
16
+
17
+ export const useDeepCompareEffect = (
18
+ effect: React.EffectCallback,
19
+ deps: React.DependencyList,
20
+ ) => {
21
+ const ref = useRef<React.DependencyList>();
22
+ let callback: ReturnType<React.EffectCallback> = () => {};
23
+ if (!isEqual(deps, ref.current)) {
24
+ callback = effect();
25
+ ref.current = deps;
26
+ }
27
+ useEffect(() => {
28
+ return () => {
29
+ if (callback) {
30
+ callback();
31
+ }
32
+ };
33
+ }, []);
34
+ };
web/src/hooks/knowledgeHook.ts CHANGED
@@ -125,11 +125,18 @@ export const useFetchKnowledgeBaseConfiguration = () => {
125
  }, [fetchKnowledgeBaseConfiguration]);
126
  };
127
 
128
- export const useFetchKnowledgeList = (): IKnowledge[] => {
 
 
129
  const dispatch = useDispatch();
130
 
131
  const knowledgeModel = useSelector((state: any) => state.knowledgeModel);
132
  const { data = [] } = knowledgeModel;
 
 
 
 
 
133
 
134
  const fetchList = useCallback(() => {
135
  dispatch({
@@ -141,5 +148,5 @@ export const useFetchKnowledgeList = (): IKnowledge[] => {
141
  fetchList();
142
  }, [fetchList]);
143
 
144
- return data;
145
  };
 
125
  }, [fetchKnowledgeBaseConfiguration]);
126
  };
127
 
128
+ export const useFetchKnowledgeList = (
129
+ shouldFilterListWithoutDocument: boolean = false,
130
+ ): IKnowledge[] => {
131
  const dispatch = useDispatch();
132
 
133
  const knowledgeModel = useSelector((state: any) => state.knowledgeModel);
134
  const { data = [] } = knowledgeModel;
135
+ const list = useMemo(() => {
136
+ return shouldFilterListWithoutDocument
137
+ ? data.filter((x: IKnowledge) => x.doc_num > 0)
138
+ : data;
139
+ }, [data, shouldFilterListWithoutDocument]);
140
 
141
  const fetchList = useCallback(() => {
142
  dispatch({
 
148
  fetchList();
149
  }, [fetchList]);
150
 
151
+ return list;
152
  };
web/src/interfaces/database/chat.ts CHANGED
@@ -1,3 +1,5 @@
 
 
1
  export interface PromptConfig {
2
  empty_response: string;
3
  parameters: Parameter[];
@@ -45,3 +47,20 @@ export interface IDialog {
45
  update_date: string;
46
  update_time: number;
47
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { MessageType } from '@/constants/chat';
2
+
3
  export interface PromptConfig {
4
  empty_response: string;
5
  parameters: Parameter[];
 
47
  update_date: string;
48
  update_time: number;
49
  }
50
+
51
+ export interface IConversation {
52
+ create_date: string;
53
+ create_time: number;
54
+ dialog_id: string;
55
+ id: string;
56
+ message: Message[];
57
+ reference: any[];
58
+ name: string;
59
+ update_date: string;
60
+ update_time: number;
61
+ }
62
+
63
+ export interface Message {
64
+ content: string;
65
+ role: MessageType;
66
+ }
web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx CHANGED
@@ -75,9 +75,7 @@ export const ParsingStatusCell = ({ record }: IProps) => {
75
 
76
  return (
77
  <Flex justify={'space-between'}>
78
- <Popover
79
- content={isRunning && <PopoverContent record={record}></PopoverContent>}
80
- >
81
  <Tag color={runningStatus.color}>
82
  {isRunning ? (
83
  <Space>
 
75
 
76
  return (
77
  <Flex justify={'space-between'}>
78
+ <Popover content={<PopoverContent record={record}></PopoverContent>}>
 
 
79
  <Tag color={runningStatus.color}>
80
  {isRunning ? (
81
  <Space>
web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx CHANGED
@@ -1,15 +1,13 @@
1
  import { Form, Input, Select } from 'antd';
2
 
3
  import classNames from 'classnames';
4
- import { ISegmentedContentProps } from './interface';
5
 
6
  import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
7
  import styles from './index.less';
8
 
9
- const { Option } = Select;
10
-
11
  const AssistantSetting = ({ show }: ISegmentedContentProps) => {
12
- const knowledgeList = useFetchKnowledgeList();
13
  const knowledgeOptions = knowledgeList.map((x) => ({
14
  label: x.name,
15
  value: x.id,
 
1
  import { Form, Input, Select } from 'antd';
2
 
3
  import classNames from 'classnames';
4
+ import { ISegmentedContentProps } from '../interface';
5
 
6
  import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
7
  import styles from './index.less';
8
 
 
 
9
  const AssistantSetting = ({ show }: ISegmentedContentProps) => {
10
+ const knowledgeList = useFetchKnowledgeList(true);
11
  const knowledgeOptions = knowledgeList.map((x) => ({
12
  label: x.name,
13
  value: x.id,
web/src/pages/chat/chat-configuration-modal/index.tsx CHANGED
@@ -3,13 +3,16 @@ import { IModalManagerChildrenProps } from '@/components/modal-manager';
3
  import { Divider, Flex, Form, Modal, Segmented } from 'antd';
4
  import { SegmentedValue } from 'antd/es/segmented';
5
  import omit from 'lodash/omit';
6
- import { useRef, useState } from 'react';
7
  import AssistantSetting from './assistant-setting';
8
  import ModelSetting from './model-setting';
9
  import PromptEngine from './prompt-engine';
10
 
11
- import { useSetDialog } from '../hooks';
12
- import { variableEnabledFieldMap } from './constants';
 
 
 
13
  import styles from './index.less';
14
 
15
  enum ConfigurationSegmented {
@@ -40,32 +43,46 @@ const validateMessages = {
40
  },
41
  };
42
 
43
- const ChatConfigurationModal = ({
44
- visible,
45
- hideModal,
46
- }: IModalManagerChildrenProps) => {
 
47
  const [form] = Form.useForm();
48
  const [value, setValue] = useState<ConfigurationSegmented>(
49
  ConfigurationSegmented.AssistantSetting,
50
  );
51
- const promptEngineRef = useRef(null);
 
52
 
53
  const setDialog = useSetDialog();
 
 
54
 
55
  const handleOk = async () => {
56
  const values = await form.validateFields();
57
- const nextValues: any = omit(values, Object.keys(variableEnabledFieldMap));
 
 
 
 
 
58
  const finalValues = {
 
59
  ...nextValues,
60
  prompt_config: {
61
  ...nextValues.prompt_config,
62
  parameters: promptEngineRef.current,
 
63
  },
64
  };
65
  console.info(promptEngineRef.current);
66
  console.info(nextValues);
67
  console.info(finalValues);
68
- setDialog(finalValues);
 
 
 
69
  };
70
 
71
  const handleCancel = () => {
@@ -76,6 +93,11 @@ const ChatConfigurationModal = ({
76
  setValue(val as ConfigurationSegmented);
77
  };
78
 
 
 
 
 
 
79
  const title = (
80
  <Flex gap={16}>
81
  <ChatConfigurationAtom></ChatConfigurationAtom>
@@ -89,6 +111,10 @@ const ChatConfigurationModal = ({
89
  </Flex>
90
  );
91
 
 
 
 
 
92
  return (
93
  <Modal
94
  title={title}
@@ -96,6 +122,9 @@ const ChatConfigurationModal = ({
96
  open={visible}
97
  onOk={handleOk}
98
  onCancel={handleCancel}
 
 
 
99
  >
100
  <Segmented
101
  size={'large'}
 
3
  import { Divider, Flex, Form, Modal, Segmented } from 'antd';
4
  import { SegmentedValue } from 'antd/es/segmented';
5
  import omit from 'lodash/omit';
6
+ import { useEffect, useRef, useState } from 'react';
7
  import AssistantSetting from './assistant-setting';
8
  import ModelSetting from './model-setting';
9
  import PromptEngine from './prompt-engine';
10
 
11
+ import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
12
+ import { variableEnabledFieldMap } from '../constants';
13
+ import { useFetchDialog, useResetCurrentDialog, useSetDialog } from '../hooks';
14
+ import { IPromptConfigParameters } from '../interface';
15
+ import { excludeUnEnabledVariables } from '../utils';
16
  import styles from './index.less';
17
 
18
  enum ConfigurationSegmented {
 
43
  },
44
  };
45
 
46
+ interface IProps extends IModalManagerChildrenProps {
47
+ id: string;
48
+ }
49
+
50
+ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => {
51
  const [form] = Form.useForm();
52
  const [value, setValue] = useState<ConfigurationSegmented>(
53
  ConfigurationSegmented.AssistantSetting,
54
  );
55
+ const promptEngineRef = useRef<Array<IPromptConfigParameters>>([]);
56
+ const loading = useOneNamespaceEffectsLoading('chatModel', ['setDialog']);
57
 
58
  const setDialog = useSetDialog();
59
+ const currentDialog = useFetchDialog(id, visible);
60
+ const { resetCurrentDialog } = useResetCurrentDialog();
61
 
62
  const handleOk = async () => {
63
  const values = await form.validateFields();
64
+ const nextValues: any = omit(values, [
65
+ ...Object.keys(variableEnabledFieldMap),
66
+ 'parameters',
67
+ ...excludeUnEnabledVariables(values),
68
+ ]);
69
+ const emptyResponse = nextValues.prompt_config?.empty_response ?? '';
70
  const finalValues = {
71
+ dialog_id: id,
72
  ...nextValues,
73
  prompt_config: {
74
  ...nextValues.prompt_config,
75
  parameters: promptEngineRef.current,
76
+ empty_response: emptyResponse,
77
  },
78
  };
79
  console.info(promptEngineRef.current);
80
  console.info(nextValues);
81
  console.info(finalValues);
82
+ const retcode: number = await setDialog(finalValues);
83
+ if (retcode === 0) {
84
+ hideModal();
85
+ }
86
  };
87
 
88
  const handleCancel = () => {
 
93
  setValue(val as ConfigurationSegmented);
94
  };
95
 
96
+ const handleModalAfterClose = () => {
97
+ resetCurrentDialog();
98
+ form.resetFields();
99
+ };
100
+
101
  const title = (
102
  <Flex gap={16}>
103
  <ChatConfigurationAtom></ChatConfigurationAtom>
 
111
  </Flex>
112
  );
113
 
114
+ useEffect(() => {
115
+ form.setFieldsValue(currentDialog);
116
+ }, [currentDialog, form]);
117
+
118
  return (
119
  <Modal
120
  title={title}
 
122
  open={visible}
123
  onOk={handleOk}
124
  onCancel={handleCancel}
125
+ confirmLoading={loading}
126
+ destroyOnClose
127
+ afterClose={handleModalAfterClose}
128
  >
129
  <Segmented
130
  size={'large'}
web/src/pages/chat/chat-configuration-modal/model-setting.tsx CHANGED
@@ -6,10 +6,10 @@ import {
6
  import { Divider, Flex, Form, InputNumber, Select, Slider, Switch } from 'antd';
7
  import classNames from 'classnames';
8
  import { useEffect } from 'react';
9
- import { ISegmentedContentProps } from './interface';
10
 
11
  import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks';
12
- import { variableEnabledFieldMap } from './constants';
13
  import styles from './index.less';
14
 
15
  const ModelSetting = ({ show, form }: ISegmentedContentProps) => {
 
6
  import { Divider, Flex, Form, InputNumber, Select, Slider, Switch } from 'antd';
7
  import classNames from 'classnames';
8
  import { useEffect } from 'react';
9
+ import { ISegmentedContentProps } from '../interface';
10
 
11
  import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks';
12
+ import { variableEnabledFieldMap } from '../constants';
13
  import styles from './index.less';
14
 
15
  const ModelSetting = ({ show, form }: ISegmentedContentProps) => {
web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx CHANGED
@@ -21,17 +21,16 @@ import {
21
  useState,
22
  } from 'react';
23
  import { v4 as uuid } from 'uuid';
 
 
 
 
 
24
  import { EditableCell, EditableRow } from './editable-cell';
25
- import { ISegmentedContentProps } from './interface';
26
 
 
27
  import styles from './index.less';
28
 
29
- interface DataType {
30
- key: string;
31
- variable: string;
32
- optional: boolean;
33
- }
34
-
35
  type FieldType = {
36
  similarity_threshold?: number;
37
  vector_similarity_weight?: number;
@@ -39,10 +38,11 @@ type FieldType = {
39
  };
40
 
41
  const PromptEngine = (
42
- { show, form }: ISegmentedContentProps,
43
- ref: ForwardedRef<Array<Omit<DataType, 'variable'>>>,
44
  ) => {
45
  const [dataSource, setDataSource] = useState<DataType[]>([]);
 
46
 
47
  const components = {
48
  body: {
@@ -99,12 +99,6 @@ const PromptEngine = (
99
  [dataSource],
100
  );
101
 
102
- useEffect(() => {
103
- form.setFieldValue(['prompt_config', 'parameters'], dataSource);
104
- const x = form.getFieldValue(['prompt_config', 'parameters']);
105
- console.info(x);
106
- }, [dataSource, form]);
107
-
108
  const columns: TableProps<DataType>['columns'] = [
109
  {
110
  title: 'key',
@@ -146,6 +140,10 @@ const PromptEngine = (
146
  },
147
  ];
148
 
 
 
 
 
149
  return (
150
  <section
151
  className={classNames({
@@ -153,7 +151,7 @@ const PromptEngine = (
153
  })}
154
  >
155
  <Form.Item
156
- label="Orchestrate"
157
  rules={[{ required: true, message: 'Please input!' }]}
158
  name={['prompt_config', 'system']}
159
  initialValue={`你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。
@@ -161,7 +159,7 @@ const PromptEngine = (
161
  {knowledge}
162
  以上是知识库。`}
163
  >
164
- <Input.TextArea autoSize={{ maxRows: 5, minRows: 5 }} />
165
  </Form.Item>
166
  <Divider></Divider>
167
  <SimilaritySlider></SimilaritySlider>
 
21
  useState,
22
  } from 'react';
23
  import { v4 as uuid } from 'uuid';
24
+ import {
25
+ VariableTableDataType as DataType,
26
+ IPromptConfigParameters,
27
+ ISegmentedContentProps,
28
+ } from '../interface';
29
  import { EditableCell, EditableRow } from './editable-cell';
 
30
 
31
+ import { useSelectPromptConfigParameters } from '../hooks';
32
  import styles from './index.less';
33
 
 
 
 
 
 
 
34
  type FieldType = {
35
  similarity_threshold?: number;
36
  vector_similarity_weight?: number;
 
38
  };
39
 
40
  const PromptEngine = (
41
+ { show }: ISegmentedContentProps,
42
+ ref: ForwardedRef<Array<IPromptConfigParameters>>,
43
  ) => {
44
  const [dataSource, setDataSource] = useState<DataType[]>([]);
45
+ const parameters = useSelectPromptConfigParameters();
46
 
47
  const components = {
48
  body: {
 
99
  [dataSource],
100
  );
101
 
 
 
 
 
 
 
102
  const columns: TableProps<DataType>['columns'] = [
103
  {
104
  title: 'key',
 
140
  },
141
  ];
142
 
143
+ useEffect(() => {
144
+ setDataSource(parameters);
145
+ }, [parameters]);
146
+
147
  return (
148
  <section
149
  className={classNames({
 
151
  })}
152
  >
153
  <Form.Item
154
+ label="System"
155
  rules={[{ required: true, message: 'Please input!' }]}
156
  name={['prompt_config', 'system']}
157
  initialValue={`你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。
 
159
  {knowledge}
160
  以上是知识库。`}
161
  >
162
+ <Input.TextArea autoSize={{ maxRows: 8, minRows: 5 }} />
163
  </Form.Item>
164
  <Divider></Divider>
165
  <SimilaritySlider></SimilaritySlider>
web/src/pages/chat/chat-container/index.less CHANGED
@@ -1,3 +1,18 @@
1
  .chatContainer {
2
  padding: 0 24px 24px;
3
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  .chatContainer {
2
  padding: 0 24px 24px;
3
  }
4
+
5
+ .messageItem {
6
+ .messageItemContent {
7
+ display: inline-block;
8
+ width: 300px;
9
+ }
10
+ }
11
+
12
+ .messageItemLeft {
13
+ text-align: left;
14
+ }
15
+
16
+ .messageItemRight {
17
+ text-align: right;
18
+ }
web/src/pages/chat/chat-container/index.tsx CHANGED
@@ -1,13 +1,41 @@
1
- import { Button, Flex, Input } from 'antd';
2
  import { ChangeEventHandler, useState } from 'react';
3
 
 
 
 
 
 
 
4
  import styles from './index.less';
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  const ChatContainer = () => {
7
  const [value, setValue] = useState('');
 
 
8
 
9
  const handlePressEnter = () => {
10
  console.info(value);
 
11
  };
12
 
13
  const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
@@ -16,7 +44,11 @@ const ChatContainer = () => {
16
 
17
  return (
18
  <Flex flex={1} className={styles.chatContainer} vertical>
19
- <Flex flex={1}>xx</Flex>
 
 
 
 
20
  <Input
21
  size="large"
22
  placeholder="Message Resume Assistant..."
 
1
+ import { Button, Flex, Input, Typography } from 'antd';
2
  import { ChangeEventHandler, useState } from 'react';
3
 
4
+ import { Message } from '@/interfaces/database/chat';
5
+ import classNames from 'classnames';
6
+ import { useFetchConversation, useSendMessage } from '../hooks';
7
+
8
+ import { MessageType } from '@/constants/chat';
9
+ import { IClientConversation } from '../interface';
10
  import styles from './index.less';
11
 
12
+ const { Paragraph } = Typography;
13
+
14
+ const MessageItem = ({ item }: { item: Message }) => {
15
+ return (
16
+ <div
17
+ className={classNames(styles.messageItem, {
18
+ [styles.messageItemLeft]: item.role === MessageType.Assistant,
19
+ [styles.messageItemRight]: item.role === MessageType.User,
20
+ })}
21
+ >
22
+ <span className={styles.messageItemContent}>
23
+ <Paragraph ellipsis={{ tooltip: item.content, rows: 3 }}>
24
+ {item.content}
25
+ </Paragraph>
26
+ </span>
27
+ </div>
28
+ );
29
+ };
30
+
31
  const ChatContainer = () => {
32
  const [value, setValue] = useState('');
33
+ const conversation: IClientConversation = useFetchConversation();
34
+ const { sendMessage } = useSendMessage();
35
 
36
  const handlePressEnter = () => {
37
  console.info(value);
38
+ sendMessage(value);
39
  };
40
 
41
  const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
 
44
 
45
  return (
46
  <Flex flex={1} className={styles.chatContainer} vertical>
47
+ <Flex flex={1} vertical>
48
+ {conversation?.message?.map((message) => (
49
+ <MessageItem key={message.id} item={message}></MessageItem>
50
+ ))}
51
+ </Flex>
52
  <Input
53
  size="large"
54
  placeholder="Message Resume Assistant..."
web/src/pages/chat/{chat-configuration-modal/constants.ts → constants.ts} RENAMED
@@ -5,3 +5,10 @@ export const variableEnabledFieldMap = {
5
  frequencyPenaltyEnabled: 'frequency_penalty',
6
  maxTokensEnabled: 'max_tokens',
7
  };
 
 
 
 
 
 
 
 
5
  frequencyPenaltyEnabled: 'frequency_penalty',
6
  maxTokensEnabled: 'max_tokens',
7
  };
8
+
9
+ export enum ChatSearchParams {
10
+ DialogId = 'dialogId',
11
+ ConversationId = 'conversationId',
12
+ }
13
+
14
+ export const EmptyConversationId = 'empty';
web/src/pages/chat/hooks.ts CHANGED
@@ -1,6 +1,16 @@
 
 
1
  import { IDialog } from '@/interfaces/database/chat';
2
- import { useCallback, useEffect } from 'react';
3
- import { useDispatch, useSelector } from 'umi';
 
 
 
 
 
 
 
 
4
 
5
  export const useFetchDialogList = () => {
6
  const dispatch = useDispatch();
@@ -20,10 +30,336 @@ export const useSetDialog = () => {
20
 
21
  const setDialog = useCallback(
22
  (payload: IDialog) => {
23
- dispatch({ type: 'chatModel/setDialog', payload });
24
  },
25
  [dispatch],
26
  );
27
 
28
  return setDialog;
29
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import showDeleteConfirm from '@/components/deleting-confirm';
2
+ import { MessageType } from '@/constants/chat';
3
  import { IDialog } from '@/interfaces/database/chat';
4
+ import omit from 'lodash/omit';
5
+ import { useCallback, useEffect, useMemo } from 'react';
6
+ import { useDispatch, useSearchParams, useSelector } from 'umi';
7
+ import { v4 as uuid } from 'uuid';
8
+ import { ChatSearchParams, EmptyConversationId } from './constants';
9
+ import {
10
+ IClientConversation,
11
+ IMessage,
12
+ VariableTableDataType,
13
+ } from './interface';
14
 
15
  export const useFetchDialogList = () => {
16
  const dispatch = useDispatch();
 
30
 
31
  const setDialog = useCallback(
32
  (payload: IDialog) => {
33
+ return dispatch<any>({ type: 'chatModel/setDialog', payload });
34
  },
35
  [dispatch],
36
  );
37
 
38
  return setDialog;
39
  };
40
+
41
+ export const useFetchDialog = (dialogId: string, visible: boolean): IDialog => {
42
+ const dispatch = useDispatch();
43
+ const currentDialog: IDialog = useSelector(
44
+ (state: any) => state.chatModel.currentDialog,
45
+ );
46
+
47
+ const fetchDialog = useCallback(() => {
48
+ if (dialogId) {
49
+ dispatch({
50
+ type: 'chatModel/getDialog',
51
+ payload: { dialog_id: dialogId },
52
+ });
53
+ }
54
+ }, [dispatch, dialogId]);
55
+
56
+ useEffect(() => {
57
+ if (dialogId && visible) {
58
+ fetchDialog();
59
+ }
60
+ }, [dialogId, fetchDialog, visible]);
61
+
62
+ return currentDialog;
63
+ };
64
+
65
+ export const useSetCurrentDialog = () => {
66
+ const dispatch = useDispatch();
67
+
68
+ const currentDialog: IDialog = useSelector(
69
+ (state: any) => state.chatModel.currentDialog,
70
+ );
71
+
72
+ const setCurrentDialog = useCallback(
73
+ (dialogId: string) => {
74
+ if (dialogId) {
75
+ dispatch({
76
+ type: 'chatModel/setCurrentDialog',
77
+ payload: { id: dialogId },
78
+ });
79
+ }
80
+ },
81
+ [dispatch],
82
+ );
83
+
84
+ return { currentDialog, setCurrentDialog };
85
+ };
86
+
87
+ export const useResetCurrentDialog = () => {
88
+ const dispatch = useDispatch();
89
+
90
+ const resetCurrentDialog = useCallback(() => {
91
+ dispatch({
92
+ type: 'chatModel/setCurrentDialog',
93
+ payload: {},
94
+ });
95
+ }, [dispatch]);
96
+
97
+ return { resetCurrentDialog };
98
+ };
99
+
100
+ export const useSelectPromptConfigParameters = (): VariableTableDataType[] => {
101
+ const currentDialog: IDialog = useSelector(
102
+ (state: any) => state.chatModel.currentDialog,
103
+ );
104
+
105
+ const finalParameters: VariableTableDataType[] = useMemo(() => {
106
+ const parameters = currentDialog?.prompt_config?.parameters ?? [];
107
+ if (!currentDialog.id) {
108
+ // The newly created chat has a default parameter
109
+ return [{ key: uuid(), variable: 'knowledge', optional: false }];
110
+ }
111
+ return parameters.map((x) => ({
112
+ key: uuid(),
113
+ variable: x.key,
114
+ optional: x.optional,
115
+ }));
116
+ }, [currentDialog]);
117
+
118
+ return finalParameters;
119
+ };
120
+
121
+ export const useRemoveDialog = () => {
122
+ const dispatch = useDispatch();
123
+
124
+ const removeDocument = (dialogIds: Array<string>) => () => {
125
+ return dispatch({
126
+ type: 'chatModel/removeDialog',
127
+ payload: {
128
+ dialog_ids: dialogIds,
129
+ },
130
+ });
131
+ };
132
+
133
+ const onRemoveDialog = (dialogIds: Array<string>) => {
134
+ showDeleteConfirm({ onOk: removeDocument(dialogIds) });
135
+ };
136
+
137
+ return { onRemoveDialog };
138
+ };
139
+
140
+ export const useClickDialogCard = () => {
141
+ const [currentQueryParameters, setSearchParams] = useSearchParams();
142
+
143
+ const newQueryParameters: URLSearchParams = useMemo(() => {
144
+ return new URLSearchParams(currentQueryParameters.toString());
145
+ }, [currentQueryParameters]);
146
+
147
+ const handleClickDialog = useCallback(
148
+ (dialogId: string) => {
149
+ newQueryParameters.set(ChatSearchParams.DialogId, dialogId);
150
+ setSearchParams(newQueryParameters);
151
+ },
152
+ [newQueryParameters, setSearchParams],
153
+ );
154
+
155
+ return { handleClickDialog };
156
+ };
157
+
158
+ export const useGetChatSearchParams = () => {
159
+ const [currentQueryParameters] = useSearchParams();
160
+
161
+ return {
162
+ dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '',
163
+ conversationId:
164
+ currentQueryParameters.get(ChatSearchParams.ConversationId) || '',
165
+ };
166
+ };
167
+
168
+ export const useSelectFirstDialogOnMount = () => {
169
+ const dialogList = useFetchDialogList();
170
+ const { dialogId } = useGetChatSearchParams();
171
+
172
+ const { handleClickDialog } = useClickDialogCard();
173
+
174
+ useEffect(() => {
175
+ if (dialogList.length > 0 && !dialogId) {
176
+ handleClickDialog(dialogList[0].id);
177
+ }
178
+ }, [dialogList, handleClickDialog, dialogId]);
179
+
180
+ return dialogList;
181
+ };
182
+
183
+ //#region conversation
184
+
185
+ export const useFetchConversationList = (dialogId?: string) => {
186
+ const dispatch = useDispatch();
187
+ const conversationList: any[] = useSelector(
188
+ (state: any) => state.chatModel.conversationList,
189
+ );
190
+
191
+ const fetchConversationList = useCallback(() => {
192
+ if (dialogId) {
193
+ dispatch({
194
+ type: 'chatModel/listConversation',
195
+ payload: { dialog_id: dialogId },
196
+ });
197
+ }
198
+ }, [dispatch, dialogId]);
199
+
200
+ useEffect(() => {
201
+ fetchConversationList();
202
+ }, [fetchConversationList]);
203
+
204
+ return conversationList;
205
+ };
206
+
207
+ export const useClickConversationCard = () => {
208
+ const [currentQueryParameters, setSearchParams] = useSearchParams();
209
+ const newQueryParameters: URLSearchParams = new URLSearchParams(
210
+ currentQueryParameters.toString(),
211
+ );
212
+
213
+ const handleClickConversation = (conversationId: string) => {
214
+ newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
215
+ setSearchParams(newQueryParameters);
216
+ };
217
+
218
+ return { handleClickConversation };
219
+ };
220
+
221
+ export const useCreateTemporaryConversation = () => {
222
+ const dispatch = useDispatch();
223
+ const { dialogId } = useGetChatSearchParams();
224
+ const { handleClickConversation } = useClickConversationCard();
225
+ let chatModel = useSelector((state: any) => state.chatModel);
226
+ let currentConversation: Pick<
227
+ IClientConversation,
228
+ 'id' | 'message' | 'name' | 'dialog_id'
229
+ > = chatModel.currentConversation;
230
+ let conversationList: IClientConversation[] = chatModel.conversationList;
231
+
232
+ const createTemporaryConversation = (message: string) => {
233
+ const messages = [...(currentConversation?.message ?? [])];
234
+ if (messages.some((x) => x.id === EmptyConversationId)) {
235
+ return;
236
+ }
237
+ messages.unshift({
238
+ id: EmptyConversationId,
239
+ content: message,
240
+ role: MessageType.Assistant,
241
+ });
242
+
243
+ // It’s the back-end data.
244
+ if ('id' in currentConversation) {
245
+ currentConversation = { ...currentConversation, message: messages };
246
+ } else {
247
+ // client data
248
+ currentConversation = {
249
+ id: EmptyConversationId,
250
+ name: 'New conversation',
251
+ dialog_id: dialogId,
252
+ message: messages,
253
+ };
254
+ }
255
+
256
+ const nextConversationList = [...conversationList];
257
+
258
+ nextConversationList.push(currentConversation as IClientConversation);
259
+
260
+ dispatch({
261
+ type: 'chatModel/setCurrentConversation',
262
+ payload: currentConversation,
263
+ });
264
+
265
+ dispatch({
266
+ type: 'chatModel/setConversationList',
267
+ payload: nextConversationList,
268
+ });
269
+ handleClickConversation(EmptyConversationId);
270
+ };
271
+
272
+ return { createTemporaryConversation };
273
+ };
274
+
275
+ export const useSetConversation = () => {
276
+ const dispatch = useDispatch();
277
+ const { dialogId } = useGetChatSearchParams();
278
+
279
+ const setConversation = (message: string) => {
280
+ return dispatch<any>({
281
+ type: 'chatModel/setConversation',
282
+ payload: {
283
+ // conversation_id: '',
284
+ dialog_id: dialogId,
285
+ name: message,
286
+ message: [
287
+ {
288
+ role: MessageType.Assistant,
289
+ content: message,
290
+ },
291
+ ],
292
+ },
293
+ });
294
+ };
295
+
296
+ return { setConversation };
297
+ };
298
+
299
+ export const useFetchConversation = () => {
300
+ const dispatch = useDispatch();
301
+ const { conversationId } = useGetChatSearchParams();
302
+ const conversation = useSelector(
303
+ (state: any) => state.chatModel.currentConversation,
304
+ );
305
+
306
+ const fetchConversation = useCallback(() => {
307
+ if (conversationId !== EmptyConversationId && conversationId !== '') {
308
+ dispatch({
309
+ type: 'chatModel/getConversation',
310
+ payload: {
311
+ conversation_id: conversationId,
312
+ },
313
+ });
314
+ }
315
+ }, [dispatch, conversationId]);
316
+
317
+ useEffect(() => {
318
+ fetchConversation();
319
+ }, [fetchConversation]);
320
+
321
+ return conversation;
322
+ };
323
+
324
+ export const useSendMessage = () => {
325
+ const dispatch = useDispatch();
326
+ const { setConversation } = useSetConversation();
327
+ const { conversationId } = useGetChatSearchParams();
328
+ const conversation = useSelector(
329
+ (state: any) => state.chatModel.currentConversation,
330
+ );
331
+ const { handleClickConversation } = useClickConversationCard();
332
+
333
+ const sendMessage = (message: string, id?: string) => {
334
+ dispatch({
335
+ type: 'chatModel/completeConversation',
336
+ payload: {
337
+ conversation_id: id ?? conversationId,
338
+ messages: [
339
+ ...(conversation?.message ?? []).map((x: IMessage) => omit(x, 'id')),
340
+ {
341
+ role: MessageType.User,
342
+ content: message,
343
+ },
344
+ ],
345
+ },
346
+ });
347
+ };
348
+
349
+ const handleSendMessage = async (message: string) => {
350
+ if (conversationId !== EmptyConversationId) {
351
+ sendMessage(message);
352
+ } else {
353
+ const data = await setConversation(message);
354
+ if (data.retcode === 0) {
355
+ const id = data.data.id;
356
+ handleClickConversation(id);
357
+ sendMessage(message, id);
358
+ }
359
+ }
360
+ };
361
+
362
+ return { sendMessage: handleSendMessage };
363
+ };
364
+
365
+ //#endregion
web/src/pages/chat/index.less CHANGED
@@ -5,6 +5,10 @@
5
  width: 288px;
6
  padding: 26px;
7
 
 
 
 
 
8
  .chatAppCard {
9
  :global(.ant-card-body) {
10
  padding: 10px;
@@ -15,6 +19,12 @@
15
  }
16
  }
17
  }
 
 
 
 
 
 
18
  }
19
  .chatTitleWrapper {
20
  width: 220px;
@@ -29,6 +39,19 @@
29
  padding: 5px 10px;
30
  }
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  .divider {
33
  margin: 0;
34
  height: 100%;
 
5
  width: 288px;
6
  padding: 26px;
7
 
8
+ .chatAppContent {
9
+ overflow-y: auto;
10
+ }
11
+
12
  .chatAppCard {
13
  :global(.ant-card-body) {
14
  padding: 10px;
 
19
  }
20
  }
21
  }
22
+ .chatAppCardSelected {
23
+ :global(.ant-card-body) {
24
+ background-color: @gray11;
25
+ border-radius: 8px;
26
+ }
27
+ }
28
  }
29
  .chatTitleWrapper {
30
  width: 220px;
 
39
  padding: 5px 10px;
40
  }
41
 
42
+ .chatTitleCard {
43
+ :global(.ant-card-body) {
44
+ padding: 8px;
45
+ }
46
+ }
47
+
48
+ .chatTitleCardSelected {
49
+ :global(.ant-card-body) {
50
+ background-color: @gray11;
51
+ border-radius: 8px;
52
+ }
53
+ }
54
+
55
  .divider {
56
  margin: 0;
57
  height: 100%;
web/src/pages/chat/index.tsx CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons';
2
  import {
3
  Button,
@@ -9,20 +11,39 @@ import {
9
  Space,
10
  Tag,
11
  } from 'antd';
12
- import ChatContainer from './chat-container';
13
-
14
- import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg';
15
- import ModalManager from '@/components/modal-manager';
16
  import classNames from 'classnames';
 
17
  import ChatConfigurationModal from './chat-configuration-modal';
18
- import { useFetchDialogList } from './hooks';
 
 
 
 
 
 
 
 
 
 
 
19
 
20
- import { useState } from 'react';
21
  import styles from './index.less';
22
 
23
  const Chat = () => {
24
- const dialogList = useFetchDialogList();
25
  const [activated, setActivated] = useState<string>('');
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  const handleAppCardEnter = (id: string) => () => {
28
  setActivated(id);
@@ -32,72 +53,84 @@ const Chat = () => {
32
  setActivated('');
33
  };
34
 
35
- const items: MenuProps['items'] = [
36
- {
37
- key: '1',
38
- label: (
39
- <a
40
- target="_blank"
41
- rel="noopener noreferrer"
42
- href="https://www.antgroup.com"
43
- >
44
- 1st menu item
45
- </a>
46
- ),
47
- },
48
- ];
 
 
 
 
49
 
50
- const appItems: MenuProps['items'] = [
51
  {
52
  key: '1',
 
53
  label: (
54
  <Space>
55
- <EditOutlined />
56
- Edit
57
- </Space>
58
- ),
59
- },
60
- { type: 'divider' },
61
- {
62
- key: '2',
63
- label: (
64
- <Space>
65
- <DeleteOutlined />
66
- Delete chat
67
  </Space>
68
  ),
69
  },
70
  ];
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  return (
73
  <Flex className={styles.chatWrapper}>
74
  <Flex className={styles.chatAppWrapper}>
75
  <Flex flex={1} vertical>
76
- <ModalManager>
77
- {({ visible, showModal, hideModal }) => {
78
- return (
79
- <>
80
- <Button type="primary" onClick={() => showModal()}>
81
- Create an Assistant
82
- </Button>
83
- <ChatConfigurationModal
84
- visible={visible}
85
- showModal={showModal}
86
- hideModal={hideModal}
87
- ></ChatConfigurationModal>
88
- </>
89
- );
90
- }}
91
- </ModalManager>
92
-
93
  <Divider></Divider>
94
- <Space direction={'vertical'} size={'middle'}>
95
  {dialogList.map((x) => (
96
  <Card
97
  key={x.id}
98
- className={classNames(styles.chatAppCard)}
 
 
 
99
  onMouseEnter={handleAppCardEnter(x.id)}
100
  onMouseLeave={handleAppCardLeave}
 
101
  >
102
  <Flex justify="space-between" align="center">
103
  <Space>
@@ -109,7 +142,7 @@ const Chat = () => {
109
  </Space>
110
  {activated === x.id && (
111
  <section>
112
- <Dropdown menu={{ items: appItems }}>
113
  <ChatAppCube className={styles.cubeIcon}></ChatAppCube>
114
  </Dropdown>
115
  </section>
@@ -117,7 +150,7 @@ const Chat = () => {
117
  </Flex>
118
  </Card>
119
  ))}
120
- </Space>
121
  </Flex>
122
  </Flex>
123
  <Divider type={'vertical'} className={styles.divider}></Divider>
@@ -137,11 +170,30 @@ const Chat = () => {
137
  </Dropdown>
138
  </Flex>
139
  <Divider></Divider>
140
- <section className={styles.chatTitleContent}>today</section>
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  </Flex>
142
  </Flex>
143
  <Divider type={'vertical'} className={styles.divider}></Divider>
144
  <ChatContainer></ChatContainer>
 
 
 
 
 
 
145
  </Flex>
146
  );
147
  };
 
1
+ import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg';
2
+ import { useSetModalState } from '@/hooks/commonHooks';
3
  import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons';
4
  import {
5
  Button,
 
11
  Space,
12
  Tag,
13
  } from 'antd';
 
 
 
 
14
  import classNames from 'classnames';
15
+ import { useCallback, useState } from 'react';
16
  import ChatConfigurationModal from './chat-configuration-modal';
17
+ import ChatContainer from './chat-container';
18
+ import {
19
+ useClickConversationCard,
20
+ useClickDialogCard,
21
+ useCreateTemporaryConversation,
22
+ useFetchConversationList,
23
+ useFetchDialog,
24
+ useGetChatSearchParams,
25
+ useRemoveDialog,
26
+ useSelectFirstDialogOnMount,
27
+ useSetCurrentDialog,
28
+ } from './hooks';
29
 
 
30
  import styles from './index.less';
31
 
32
  const Chat = () => {
33
+ const dialogList = useSelectFirstDialogOnMount();
34
  const [activated, setActivated] = useState<string>('');
35
+ const { visible, hideModal, showModal } = useSetModalState();
36
+ const { setCurrentDialog, currentDialog } = useSetCurrentDialog();
37
+ const { onRemoveDialog } = useRemoveDialog();
38
+ const { handleClickDialog } = useClickDialogCard();
39
+ const { handleClickConversation } = useClickConversationCard();
40
+ const { dialogId, conversationId } = useGetChatSearchParams();
41
+ const list = useFetchConversationList(dialogId);
42
+ const { createTemporaryConversation } = useCreateTemporaryConversation();
43
+
44
+ const selectedDialog = useFetchDialog(dialogId, true);
45
+
46
+ const prologue = selectedDialog?.prompt_config?.prologue || '';
47
 
48
  const handleAppCardEnter = (id: string) => () => {
49
  setActivated(id);
 
53
  setActivated('');
54
  };
55
 
56
+ const handleShowChatConfigurationModal = (dialogId?: string) => () => {
57
+ if (dialogId) {
58
+ setCurrentDialog(dialogId);
59
+ }
60
+ showModal();
61
+ };
62
+
63
+ const handleDialogCardClick = (dialogId: string) => () => {
64
+ handleClickDialog(dialogId);
65
+ };
66
+
67
+ const handleConversationCardClick = (dialogId: string) => () => {
68
+ handleClickConversation(dialogId);
69
+ };
70
+
71
+ const handleCreateTemporaryConversation = useCallback(() => {
72
+ createTemporaryConversation(prologue);
73
+ }, [createTemporaryConversation, prologue]);
74
 
75
+ const items: MenuProps['items'] = [
76
  {
77
  key: '1',
78
+ onClick: handleCreateTemporaryConversation,
79
  label: (
80
  <Space>
81
+ <EditOutlined /> New chat
 
 
 
 
 
 
 
 
 
 
 
82
  </Space>
83
  ),
84
  },
85
  ];
86
 
87
+ const buildAppItems = (dialogId: string) => {
88
+ const appItems: MenuProps['items'] = [
89
+ {
90
+ key: '1',
91
+ onClick: handleShowChatConfigurationModal(dialogId),
92
+ label: (
93
+ <Space>
94
+ <EditOutlined />
95
+ Edit
96
+ </Space>
97
+ ),
98
+ },
99
+ { type: 'divider' },
100
+ {
101
+ key: '2',
102
+ onClick: () => onRemoveDialog([dialogId]),
103
+ label: (
104
+ <Space>
105
+ <DeleteOutlined />
106
+ Delete chat
107
+ </Space>
108
+ ),
109
+ },
110
+ ];
111
+
112
+ return appItems;
113
+ };
114
+
115
  return (
116
  <Flex className={styles.chatWrapper}>
117
  <Flex className={styles.chatAppWrapper}>
118
  <Flex flex={1} vertical>
119
+ <Button type="primary" onClick={handleShowChatConfigurationModal()}>
120
+ Create an Assistant
121
+ </Button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  <Divider></Divider>
123
+ <Flex className={styles.chatAppContent} vertical gap={10}>
124
  {dialogList.map((x) => (
125
  <Card
126
  key={x.id}
127
+ hoverable
128
+ className={classNames(styles.chatAppCard, {
129
+ [styles.chatAppCardSelected]: dialogId === x.id,
130
+ })}
131
  onMouseEnter={handleAppCardEnter(x.id)}
132
  onMouseLeave={handleAppCardLeave}
133
+ onClick={handleDialogCardClick(x.id)}
134
  >
135
  <Flex justify="space-between" align="center">
136
  <Space>
 
142
  </Space>
143
  {activated === x.id && (
144
  <section>
145
+ <Dropdown menu={{ items: buildAppItems(x.id) }}>
146
  <ChatAppCube className={styles.cubeIcon}></ChatAppCube>
147
  </Dropdown>
148
  </section>
 
150
  </Flex>
151
  </Card>
152
  ))}
153
+ </Flex>
154
  </Flex>
155
  </Flex>
156
  <Divider type={'vertical'} className={styles.divider}></Divider>
 
170
  </Dropdown>
171
  </Flex>
172
  <Divider></Divider>
173
+ <Flex vertical gap={10} className={styles.chatTitleContent}>
174
+ {list.map((x) => (
175
+ <Card
176
+ key={x.id}
177
+ hoverable
178
+ onClick={handleConversationCardClick(x.id)}
179
+ className={classNames(styles.chatTitleCard, {
180
+ [styles.chatTitleCardSelected]: x.id === conversationId,
181
+ })}
182
+ >
183
+ <div>{x.name}</div>
184
+ </Card>
185
+ ))}
186
+ </Flex>
187
  </Flex>
188
  </Flex>
189
  <Divider type={'vertical'} className={styles.divider}></Divider>
190
  <ChatContainer></ChatContainer>
191
+ <ChatConfigurationModal
192
+ visible={visible}
193
+ showModal={showModal}
194
+ hideModal={hideModal}
195
+ id={currentDialog.id}
196
+ ></ChatConfigurationModal>
197
  </Flex>
198
  );
199
  };
web/src/pages/chat/{chat-configuration-modal/interface.ts → interface.ts} RENAMED
@@ -1,3 +1,4 @@
 
1
  import { FormInstance } from 'antd';
2
 
3
  export interface ISegmentedContentProps {
@@ -12,3 +13,19 @@ export interface IVariable {
12
  presence_penalty: number;
13
  max_tokens: number;
14
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { IConversation, Message } from '@/interfaces/database/chat';
2
  import { FormInstance } from 'antd';
3
 
4
  export interface ISegmentedContentProps {
 
13
  presence_penalty: number;
14
  max_tokens: number;
15
  }
16
+
17
+ export interface VariableTableDataType {
18
+ key: string;
19
+ variable: string;
20
+ optional: boolean;
21
+ }
22
+
23
+ export type IPromptConfigParameters = Omit<VariableTableDataType, 'variable'>;
24
+
25
+ export interface IMessage extends Message {
26
+ id: string;
27
+ }
28
+
29
+ export interface IClientConversation extends IConversation {
30
+ message: IMessage[];
31
+ }
web/src/pages/chat/model.ts CHANGED
@@ -1,11 +1,16 @@
1
- import { IDialog } from '@/interfaces/database/chat';
2
  import chatService from '@/services/chatService';
3
  import { message } from 'antd';
4
  import { DvaModel } from 'umi';
 
 
5
 
6
  export interface ChatModelState {
7
  name: string;
8
  dialogList: IDialog[];
 
 
 
9
  }
10
 
11
  const model: DvaModel<ChatModelState> = {
@@ -13,6 +18,9 @@ const model: DvaModel<ChatModelState> = {
13
  state: {
14
  name: 'kate',
15
  dialogList: [],
 
 
 
16
  },
17
  reducers: {
18
  save(state, action) {
@@ -27,11 +35,50 @@ const model: DvaModel<ChatModelState> = {
27
  dialogList: payload,
28
  };
29
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  },
31
 
32
  effects: {
33
  *getDialog({ payload }, { call, put }) {
34
  const { data } = yield call(chatService.getDialog, payload);
 
 
 
35
  },
36
  *setDialog({ payload }, { call, put }) {
37
  const { data } = yield call(chatService.setDialog, payload);
@@ -39,6 +86,15 @@ const model: DvaModel<ChatModelState> = {
39
  yield put({ type: 'listDialog' });
40
  message.success('Created successfully !');
41
  }
 
 
 
 
 
 
 
 
 
42
  },
43
  *listDialog({ payload }, { call, put }) {
44
  const { data } = yield call(chatService.listDialog, payload);
@@ -46,15 +102,40 @@ const model: DvaModel<ChatModelState> = {
46
  },
47
  *listConversation({ payload }, { call, put }) {
48
  const { data } = yield call(chatService.listConversation, payload);
 
 
 
 
49
  },
50
  *getConversation({ payload }, { call, put }) {
51
  const { data } = yield call(chatService.getConversation, payload);
 
 
 
 
52
  },
53
  *setConversation({ payload }, { call, put }) {
54
  const { data } = yield call(chatService.setConversation, payload);
 
 
 
 
 
 
 
 
 
55
  },
56
  *completeConversation({ payload }, { call, put }) {
57
  const { data } = yield call(chatService.completeConversation, payload);
 
 
 
 
 
 
 
 
58
  },
59
  },
60
  };
 
1
+ import { IConversation, IDialog, Message } from '@/interfaces/database/chat';
2
  import chatService from '@/services/chatService';
3
  import { message } from 'antd';
4
  import { DvaModel } from 'umi';
5
+ import { v4 as uuid } from 'uuid';
6
+ import { IClientConversation, IMessage } from './interface';
7
 
8
  export interface ChatModelState {
9
  name: string;
10
  dialogList: IDialog[];
11
+ currentDialog: IDialog;
12
+ conversationList: IConversation[];
13
+ currentConversation: IClientConversation;
14
  }
15
 
16
  const model: DvaModel<ChatModelState> = {
 
18
  state: {
19
  name: 'kate',
20
  dialogList: [],
21
+ currentDialog: <IDialog>{},
22
+ conversationList: [],
23
+ currentConversation: {} as IClientConversation,
24
  },
25
  reducers: {
26
  save(state, action) {
 
35
  dialogList: payload,
36
  };
37
  },
38
+ setCurrentDialog(state, { payload }) {
39
+ return {
40
+ ...state,
41
+ currentDialog: payload,
42
+ };
43
+ },
44
+ setConversationList(state, { payload }) {
45
+ return {
46
+ ...state,
47
+ conversationList: payload,
48
+ };
49
+ },
50
+ setCurrentConversation(state, { payload }) {
51
+ const messageList = payload?.message.map((x: Message | IMessage) => ({
52
+ ...x,
53
+ id: 'id' in x ? x.id : uuid(),
54
+ }));
55
+ return {
56
+ ...state,
57
+ currentConversation: { ...payload, message: messageList },
58
+ };
59
+ },
60
+ addEmptyConversationToList(state, {}) {
61
+ const list = [...state.conversationList];
62
+ // if (list.every((x) => x.id !== 'empty')) {
63
+ // list.push({
64
+ // id: 'empty',
65
+ // name: 'New conversation',
66
+ // message: [],
67
+ // });
68
+ // }
69
+ return {
70
+ ...state,
71
+ conversationList: list,
72
+ };
73
+ },
74
  },
75
 
76
  effects: {
77
  *getDialog({ payload }, { call, put }) {
78
  const { data } = yield call(chatService.getDialog, payload);
79
+ if (data.retcode === 0) {
80
+ yield put({ type: 'setCurrentDialog', payload: data.data });
81
+ }
82
  },
83
  *setDialog({ payload }, { call, put }) {
84
  const { data } = yield call(chatService.setDialog, payload);
 
86
  yield put({ type: 'listDialog' });
87
  message.success('Created successfully !');
88
  }
89
+ return data.retcode;
90
+ },
91
+ *removeDialog({ payload }, { call, put }) {
92
+ const { data } = yield call(chatService.removeDialog, payload);
93
+ if (data.retcode === 0) {
94
+ yield put({ type: 'listDialog' });
95
+ message.success('Deleted successfully !');
96
+ }
97
+ return data.retcode;
98
  },
99
  *listDialog({ payload }, { call, put }) {
100
  const { data } = yield call(chatService.listDialog, payload);
 
102
  },
103
  *listConversation({ payload }, { call, put }) {
104
  const { data } = yield call(chatService.listConversation, payload);
105
+ if (data.retcode === 0) {
106
+ yield put({ type: 'setConversationList', payload: data.data });
107
+ }
108
+ return data.retcode;
109
  },
110
  *getConversation({ payload }, { call, put }) {
111
  const { data } = yield call(chatService.getConversation, payload);
112
+ if (data.retcode === 0) {
113
+ yield put({ type: 'setCurrentConversation', payload: data.data });
114
+ }
115
+ return data.retcode;
116
  },
117
  *setConversation({ payload }, { call, put }) {
118
  const { data } = yield call(chatService.setConversation, payload);
119
+ if (data.retcode === 0) {
120
+ yield put({
121
+ type: 'listConversation',
122
+ payload: {
123
+ dialog_id: data.data.dialog_id,
124
+ },
125
+ });
126
+ }
127
+ return data;
128
  },
129
  *completeConversation({ payload }, { call, put }) {
130
  const { data } = yield call(chatService.completeConversation, payload);
131
+ if (data.retcode === 0) {
132
+ yield put({
133
+ type: 'getConversation',
134
+ payload: {
135
+ conversation_id: payload.conversation_id,
136
+ },
137
+ });
138
+ }
139
  },
140
  },
141
  };
web/src/pages/chat/utils.ts ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { variableEnabledFieldMap } from './constants';
2
+
3
+ export const excludeUnEnabledVariables = (values: any) => {
4
+ const unEnabledFields: Array<keyof typeof variableEnabledFieldMap> =
5
+ Object.keys(variableEnabledFieldMap).filter((key) => !values[key]) as Array<
6
+ keyof typeof variableEnabledFieldMap
7
+ >;
8
+
9
+ return unEnabledFields.map(
10
+ (key) => `llm_setting.${variableEnabledFieldMap[key]}`,
11
+ );
12
+ };
web/src/pages/knowledge/model.ts CHANGED
@@ -31,7 +31,7 @@ const model: DvaModel<KnowledgeModelState> = {
31
  },
32
  *getList({ payload = {} }, { call, put }) {
33
  const { data } = yield call(kbService.getList, payload);
34
- const { retcode, data: res, retmsg } = data;
35
 
36
  if (retcode === 0) {
37
  yield put({
 
31
  },
32
  *getList({ payload = {} }, { call, put }) {
33
  const { data } = yield call(kbService.getList, payload);
34
+ const { retcode, data: res } = data;
35
 
36
  if (retcode === 0) {
37
  yield put({
web/src/services/chatService.ts CHANGED
@@ -6,6 +6,7 @@ const {
6
  getDialog,
7
  setDialog,
8
  listDialog,
 
9
  getConversation,
10
  setConversation,
11
  completeConversation,
@@ -21,6 +22,10 @@ const methods = {
21
  url: setDialog,
22
  method: 'post',
23
  },
 
 
 
 
24
  listDialog: {
25
  url: listDialog,
26
  method: 'get',
 
6
  getDialog,
7
  setDialog,
8
  listDialog,
9
+ removeDialog,
10
  getConversation,
11
  setConversation,
12
  completeConversation,
 
22
  url: setDialog,
23
  method: 'post',
24
  },
25
+ removeDialog: {
26
+ url: removeDialog,
27
+ method: 'post',
28
+ },
29
  listDialog: {
30
  url: listDialog,
31
  method: 'get',
web/src/utils/api.ts CHANGED
@@ -45,6 +45,7 @@ export default {
45
 
46
  setDialog: `${api_host}/dialog/set`,
47
  getDialog: `${api_host}/dialog/get`,
 
48
  listDialog: `${api_host}/dialog/list`,
49
 
50
  setConversation: `${api_host}/conversation/set`,
 
45
 
46
  setDialog: `${api_host}/dialog/set`,
47
  getDialog: `${api_host}/dialog/get`,
48
+ removeDialog: `${api_host}/dialog/rm`,
49
  listDialog: `${api_host}/dialog/list`,
50
 
51
  setConversation: `${api_host}/conversation/set`,