From 7c0fb078f8fb2f7a5e0e588b0514131d8187b545 Mon Sep 17 00:00:00 2001
From: balibabu <cike8899@users.noreply.github.com>
Date: Mon, 11 Mar 2024 16:13:34 +0800
Subject: [PATCH] feat: display chunk token number when category of knowledge
 as general and unavailable llm models appear disabled and if the backend
 returns 401, it will jump to the login page and fixed the issue where the
 greeting would disappear when clicking on a new dialog (#117)

* feat: Fixed the issue where the greeting would disappear when clicking on a new dialog

* feat: replace favicon with logo.svg

* feat: if the backend returns 401, it will jump to the login page.

* feat: unavailable llm models appear disabled

* feat: display chunk token number when category of knowledge as general
---
 web/.umirc.ts                                 |  1 +
 web/public/logo.svg                           | 29 ++++++
 web/src/hooks/llmHooks.ts                     |  1 +
 .../knowledge-setting/configuration.tsx       | 48 +++++++++
 .../components/knowledge-setting/index.less   |  3 +
 .../chat/chat-configuration-modal/index.tsx   | 50 +++++-----
 .../model-setting.tsx                         |  2 +-
 web/src/pages/chat/hooks.ts                   | 99 +++++++++++++++----
 web/src/pages/chat/index.tsx                  | 30 ++++--
 web/src/pages/chat/model.ts                   | 11 ++-
 web/src/utils/history.ts                      |  3 -
 web/src/utils/request.ts                      | 11 +--
 12 files changed, 219 insertions(+), 69 deletions(-)
 create mode 100644 web/public/logo.svg
 delete mode 100644 web/src/utils/history.ts

diff --git a/web/.umirc.ts b/web/.umirc.ts
index 6c72be6..7b36b57 100644
--- a/web/.umirc.ts
+++ b/web/.umirc.ts
@@ -11,6 +11,7 @@ export default defineConfig({
   esbuildMinifyIIFE: true,
   icons: {},
   hash: true,
+  favicons: ['/logo.svg'],
   history: {
     type: 'browser',
   },
diff --git a/web/public/logo.svg b/web/public/logo.svg
new file mode 100644
index 0000000..54167d2
--- /dev/null
+++ b/web/public/logo.svg
@@ -0,0 +1,29 @@
+<svg width="32" height="34" viewBox="0 0 32 34" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="evenodd" clip-rule="evenodd"
+        d="M3.43265 20.7677C4.15835 21.5062 4.15834 22.7035 3.43262 23.4419L3.39546 23.4797C2.66974 24.2182 1.49312 24.2182 0.767417 23.4797C0.0417107 22.7412 0.0417219 21.544 0.767442 20.8055L0.804608 20.7677C1.53033 20.0292 2.70694 20.0293 3.43265 20.7677Z"
+        fill="#B2DDFF" />
+    <path fill-rule="evenodd" clip-rule="evenodd"
+        d="M12.1689 21.3375C12.8933 22.0773 12.8912 23.2746 12.1641 24.0117L7.01662 29.2307C6.2896 29.9678 5.11299 29.9657 4.38859 29.2259C3.66419 28.4861 3.66632 27.2888 4.39334 26.5517L9.54085 21.3327C10.2679 20.5956 11.4445 20.5977 12.1689 21.3375Z"
+        fill="#53B1FD" />
+    <path fill-rule="evenodd" clip-rule="evenodd"
+        d="M19.1551 30.3217C19.7244 29.4528 20.8781 29.218 21.7321 29.7973L21.8436 29.8729C22.6975 30.4522 22.9283 31.6262 22.359 32.4952C21.7897 33.3641 20.6359 33.5989 19.782 33.0196L19.6705 32.944C18.8165 32.3647 18.5858 31.1907 19.1551 30.3217Z"
+        fill="#B2DDFF" />
+    <path fill-rule="evenodd" clip-rule="evenodd"
+        d="M31.4184 20.6544C32.1441 21.3929 32.1441 22.5902 31.4184 23.3286L28.8911 25.9003C28.1654 26.6388 26.9887 26.6388 26.263 25.9003C25.5373 25.1619 25.5373 23.9646 26.263 23.2261L28.7903 20.6544C29.516 19.916 30.6927 19.916 31.4184 20.6544Z"
+        fill="#53B1FD" />
+    <path fill-rule="evenodd" clip-rule="evenodd"
+        d="M31.4557 11.1427C32.1814 11.8812 32.1814 13.0785 31.4557 13.8169L12.7797 32.8209C12.054 33.5594 10.8774 33.5594 10.1517 32.8209C9.42599 32.0825 9.42599 30.8852 10.1517 30.1467L28.8277 11.1427C29.5534 10.4043 30.73 10.4043 31.4557 11.1427Z"
+        fill="#1570EF" />
+    <path fill-rule="evenodd" clip-rule="evenodd"
+        d="M27.925 5.29994C28.6508 6.0384 28.6508 7.23568 27.925 7.97414L17.184 18.9038C16.4583 19.6423 15.2817 19.6423 14.556 18.9038C13.8303 18.1653 13.8303 16.9681 14.556 16.2296L25.297 5.29994C26.0227 4.56148 27.1993 4.56148 27.925 5.29994Z"
+        fill="#1570EF" />
+    <path fill-rule="evenodd" clip-rule="evenodd"
+        d="M22.256 1.59299C22.9822 2.33095 22.983 3.52823 22.2578 4.26718L8.45055 18.3358C7.72533 19.0748 6.54871 19.0756 5.82251 18.3376C5.09631 17.5996 5.09552 16.4024 5.82075 15.6634L19.6279 1.59478C20.3532 0.855827 21.5298 0.855022 22.256 1.59299Z"
+        fill="#1570EF" />
+    <path fill-rule="evenodd" clip-rule="evenodd"
+        d="M8.58225 6.09619C9.30671 6.83592 9.30469 8.0332 8.57772 8.77038L3.17006 14.2541C2.4431 14.9913 1.26649 14.9893 0.542025 14.2495C-0.182438 13.5098 -0.180413 12.3125 0.546548 11.5753L5.95421 6.09159C6.68117 5.3544 7.85778 5.35646 8.58225 6.09619Z"
+        fill="#53B1FD" />
+    <path fill-rule="evenodd" clip-rule="evenodd"
+        d="M11.893 0.624023C12.9193 0.624023 13.7513 1.47063 13.7513 2.51497V2.70406C13.7513 3.7484 12.9193 4.59501 11.893 4.59501C10.8667 4.59501 10.0347 3.7484 10.0347 2.70406V2.51497C10.0347 1.47063 10.8667 0.624023 11.893 0.624023Z"
+        fill="#B2DDFF" />
+</svg>
\ No newline at end of file
diff --git a/web/src/hooks/llmHooks.ts b/web/src/hooks/llmHooks.ts
index 4ff3639..7800074 100644
--- a/web/src/hooks/llmHooks.ts
+++ b/web/src/hooks/llmHooks.ts
@@ -30,6 +30,7 @@ export const useSelectLlmOptions = () => {
         options: value.map((x) => ({
           label: x.llm_name,
           value: x.llm_name,
+          disabled: !x.available,
         })),
       };
     });
diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx
index f678c44..b06eda3 100644
--- a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx
+++ b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx
@@ -10,10 +10,13 @@ import {
 import {
   Button,
   Divider,
+  Flex,
   Form,
   Input,
+  InputNumber,
   Radio,
   Select,
+  Slider,
   Space,
   Typography,
   Upload,
@@ -80,6 +83,7 @@ const Configuration = () => {
         'permission',
         'embd_id',
         'parser_id',
+        'parser_config.chunk_token_num',
       ]),
       avatar: fileList,
     });
@@ -144,6 +148,7 @@ const Configuration = () => {
           name="embd_id"
           label="Embedding Model"
           rules={[{ required: true }]}
+          tooltip="xx"
         >
           <Select
             placeholder="Please select a country"
@@ -153,6 +158,7 @@ const Configuration = () => {
         <Form.Item
           name="parser_id"
           label="Knowledge base category"
+          tooltip="xx"
           rules={[{ required: true }]}
         >
           <Select placeholder="Please select a country">
@@ -163,6 +169,48 @@ const Configuration = () => {
             ))}
           </Select>
         </Form.Item>
+        <Form.Item noStyle dependencies={['parser_id']}>
+          {({ getFieldValue }) => {
+            const parserId = getFieldValue('parser_id');
+
+            if (parserId === 'general') {
+              return (
+                <Form.Item label="Chunk token number" tooltip="xxx">
+                  <Flex gap={20} align="center">
+                    <Flex flex={1}>
+                      <Form.Item
+                        name={['parser_config', 'chunk_token_num']}
+                        noStyle
+                        initialValue={128}
+                        rules={[
+                          { required: true, message: 'Province is required' },
+                        ]}
+                      >
+                        <Slider className={styles.variableSlider} max={2048} />
+                      </Form.Item>
+                    </Flex>
+                    <Form.Item
+                      name={['parser_config', 'chunk_token_num']}
+                      noStyle
+                      initialValue={128}
+                      rules={[
+                        { required: true, message: 'Street is required' },
+                      ]}
+                    >
+                      <InputNumber
+                        className={styles.sliderInputNumber}
+                        max={2048}
+                        min={0}
+                      />
+                    </Form.Item>
+                  </Flex>
+                </Form.Item>
+              );
+            }
+
+            return null;
+          }}
+        </Form.Item>
         <Form.Item>
           <div className={styles.buttonWrapper}>
             <Space>
diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/index.less b/web/src/pages/add-knowledge/components/knowledge-setting/index.less
index 0691fd9..6592e13 100644
--- a/web/src/pages/add-knowledge/components/knowledge-setting/index.less
+++ b/web/src/pages/add-knowledge/components/knowledge-setting/index.less
@@ -27,4 +27,7 @@
   .buttonWrapper {
     text-align: right;
   }
+  .variableSlider {
+    width: 100%;
+  }
 }
diff --git a/web/src/pages/chat/chat-configuration-modal/index.tsx b/web/src/pages/chat/chat-configuration-modal/index.tsx
index 9b0257a..8e0f523 100644
--- a/web/src/pages/chat/chat-configuration-modal/index.tsx
+++ b/web/src/pages/chat/chat-configuration-modal/index.tsx
@@ -1,19 +1,18 @@
 import { ReactComponent as ChatConfigurationAtom } from '@/assets/svg/chat-configuration-atom.svg';
 import { IModalManagerChildrenProps } from '@/components/modal-manager';
+import { IDialog } from '@/interfaces/database/chat';
 import { Divider, Flex, Form, Modal, Segmented, UploadFile } from 'antd';
 import { SegmentedValue } from 'antd/es/segmented';
 import omit from 'lodash/omit';
 import { useEffect, useRef, useState } from 'react';
-import AssistantSetting from './assistant-setting';
-import ModelSetting from './model-setting';
-import PromptEngine from './prompt-engine';
-
-import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
 import { variableEnabledFieldMap } from '../constants';
-import { useFetchDialog, useResetCurrentDialog, useSetDialog } from '../hooks';
 import { IPromptConfigParameters } from '../interface';
 import { excludeUnEnabledVariables } from '../utils';
+import AssistantSetting from './assistant-setting';
 import { useFetchModelId } from './hooks';
+import ModelSetting from './model-setting';
+import PromptEngine from './prompt-engine';
+
 import styles from './index.less';
 
 enum ConfigurationSegmented {
@@ -45,22 +44,27 @@ const validateMessages = {
 };
 
 interface IProps extends IModalManagerChildrenProps {
-  id: string;
+  initialDialog: IDialog;
+  loading: boolean;
+  onOk: (dialog: IDialog) => void;
+  clearDialog: () => void;
 }
 
-const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => {
+const ChatConfigurationModal = ({
+  visible,
+  hideModal,
+  initialDialog,
+  loading,
+  onOk,
+  clearDialog,
+}: IProps) => {
   const [form] = Form.useForm();
   const [value, setValue] = useState<ConfigurationSegmented>(
     ConfigurationSegmented.AssistantSetting,
   );
   const promptEngineRef = useRef<Array<IPromptConfigParameters>>([]);
-  const loading = useOneNamespaceEffectsLoading('chatModel', ['setDialog']);
   const modelId = useFetchModelId(visible);
 
-  const setDialog = useSetDialog();
-  const currentDialog = useFetchDialog(id, visible);
-  const { resetCurrentDialog } = useResetCurrentDialog();
-
   const handleOk = async () => {
     const values = await form.validateFields();
     const nextValues: any = omit(values, [
@@ -78,7 +82,7 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => {
     }
 
     const finalValues = {
-      dialog_id: id,
+      dialog_id: initialDialog.id,
       ...nextValues,
       prompt_config: {
         ...nextValues.prompt_config,
@@ -87,13 +91,7 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => {
       },
       icon,
     };
-    console.info(promptEngineRef.current);
-    console.info(nextValues);
-    console.info(finalValues);
-    const retcode: number = await setDialog(finalValues);
-    if (retcode === 0) {
-      hideModal();
-    }
+    onOk(finalValues);
   };
 
   const handleCancel = () => {
@@ -105,7 +103,7 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => {
   };
 
   const handleModalAfterClose = () => {
-    resetCurrentDialog();
+    clearDialog();
     form.resetFields();
   };
 
@@ -124,19 +122,19 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => {
 
   useEffect(() => {
     if (visible) {
-      const icon = currentDialog.icon;
+      const icon = initialDialog.icon;
       let fileList: UploadFile[] = [];
 
       if (icon) {
         fileList = [{ uid: '1', name: 'file', thumbUrl: icon, status: 'done' }];
       }
       form.setFieldsValue({
-        ...currentDialog,
+        ...initialDialog,
         icon: fileList,
-        llm_id: currentDialog.llm_id ?? modelId,
+        llm_id: initialDialog.llm_id ?? modelId,
       });
     }
-  }, [currentDialog, form, visible, modelId]);
+  }, [initialDialog, form, visible, modelId]);
 
   return (
     <Modal
diff --git a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx
index 5f68a6d..2f4c666 100644
--- a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx
+++ b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx
@@ -54,7 +54,7 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => {
         name="llm_id"
         rules={[{ required: true, message: 'Please select!' }]}
       >
-        <Select options={modelOptions} />
+        <Select options={modelOptions} showSearch />
       </Form.Item>
       <Divider></Divider>
       <Form.Item
diff --git a/web/src/pages/chat/hooks.ts b/web/src/pages/chat/hooks.ts
index 47a3721..b3e83bc 100644
--- a/web/src/pages/chat/hooks.ts
+++ b/web/src/pages/chat/hooks.ts
@@ -45,24 +45,42 @@ export const useSetDialog = () => {
   return setDialog;
 };
 
-export const useFetchDialog = (dialogId: string, visible: boolean): IDialog => {
-  const dispatch = useDispatch();
+export const useSelectCurrentDialog = () => {
   const currentDialog: IDialog = useSelector(
     (state: any) => state.chatModel.currentDialog,
   );
 
-  const fetchDialog = useCallback(() => {
-    if (dialogId) {
-      dispatch({
-        type: 'chatModel/getDialog',
-        payload: { dialog_id: dialogId },
-      });
-    }
-  }, [dispatch, dialogId]);
+  return currentDialog;
+};
+
+export const useFetchDialog = () => {
+  const dispatch = useDispatch();
+
+  const fetchDialog = useCallback(
+    (dialogId: string, needToBeSaved = true) => {
+      if (dialogId) {
+        return dispatch<any>({
+          type: 'chatModel/getDialog',
+          payload: { dialog_id: dialogId, needToBeSaved },
+        });
+      }
+    },
+    [dispatch],
+  );
+
+  return fetchDialog;
+};
+
+export const useFetchDialogOnMount = (
+  dialogId: string,
+  visible: boolean,
+): IDialog => {
+  const currentDialog: IDialog = useSelectCurrentDialog();
+  const fetchDialog = useFetchDialog();
 
   useEffect(() => {
     if (dialogId && visible) {
-      fetchDialog();
+      fetchDialog(dialogId);
     }
   }, [dialogId, fetchDialog, visible]);
 
@@ -123,14 +141,6 @@ export const useSelectPromptConfigParameters = (): VariableTableDataType[] => {
   return finalParameters;
 };
 
-export const useSelectCurrentDialog = () => {
-  const currentDialog: IDialog = useSelector(
-    (state: any) => state.chatModel.currentDialog,
-  );
-
-  return currentDialog;
-};
-
 export const useRemoveDialog = () => {
   const dispatch = useDispatch();
 
@@ -231,6 +241,57 @@ export const useHandleItemHover = () => {
   };
 };
 
+export const useEditDialog = () => {
+  const [dialog, setDialog] = useState<IDialog>({} as IDialog);
+  const fetchDialog = useFetchDialog();
+  const submitDialog = useSetDialog();
+  const loading = useOneNamespaceEffectsLoading('chatModel', ['setDialog']);
+
+  const {
+    visible: dialogEditVisible,
+    hideModal: hideDialogEditModal,
+    showModal: showDialogEditModal,
+  } = useSetModalState();
+
+  const onDialogEditOk = useCallback(
+    async (dialog: IDialog) => {
+      const ret = await submitDialog(dialog);
+
+      if (ret === 0) {
+        hideDialogEditModal();
+      }
+    },
+    [submitDialog, hideDialogEditModal],
+  );
+
+  const handleShowDialogEditModal = useCallback(
+    async (dialogId?: string) => {
+      if (dialogId) {
+        const ret = await fetchDialog(dialogId, false);
+        if (ret.retcode === 0) {
+          setDialog(ret.data);
+        }
+      }
+      showDialogEditModal();
+    },
+    [showDialogEditModal, fetchDialog],
+  );
+
+  const clearDialog = useCallback(() => {
+    setDialog({} as IDialog);
+  }, []);
+
+  return {
+    dialogSettingLoading: loading,
+    initialDialog: dialog,
+    onDialogEditOk,
+    dialogEditVisible,
+    hideDialogEditModal,
+    showDialogEditModal: handleShowDialogEditModal,
+    clearDialog,
+  };
+};
+
 //#region conversation
 
 export const useFetchConversationList = () => {
diff --git a/web/src/pages/chat/index.tsx b/web/src/pages/chat/index.tsx
index 680cd77..b6ee17a 100644
--- a/web/src/pages/chat/index.tsx
+++ b/web/src/pages/chat/index.tsx
@@ -20,8 +20,9 @@ import ChatContainer from './chat-container';
 import {
   useClickConversationCard,
   useClickDialogCard,
+  useEditDialog,
   useFetchConversationList,
-  useFetchDialog,
+  useFetchDialogOnMount,
   useGetChatSearchParams,
   useHandleItemHover,
   useRemoveConversation,
@@ -60,8 +61,17 @@ const Chat = () => {
     hideConversationRenameModal,
     showConversationRenameModal,
   } = useRenameConversation();
+  const {
+    dialogSettingLoading,
+    initialDialog,
+    onDialogEditOk,
+    dialogEditVisible,
+    clearDialog,
+    hideDialogEditModal,
+    showDialogEditModal,
+  } = useEditDialog();
 
-  useFetchDialog(dialogId, true);
+  useFetchDialogOnMount(dialogId, true);
 
   const handleAppCardEnter = (id: string) => () => {
     handleItemEnter(id);
@@ -76,10 +86,7 @@ const Chat = () => {
     (info: any) => {
       info?.domEvent?.preventDefault();
       info?.domEvent?.stopPropagation();
-      // if (dialogId) {
-      setCurrentDialog(dialogId ?? '');
-      // }
-      showModal();
+      showDialogEditModal(dialogId);
     };
 
   const handleRemoveDialog =
@@ -276,10 +283,13 @@ const Chat = () => {
       <Divider type={'vertical'} className={styles.divider}></Divider>
       <ChatContainer></ChatContainer>
       <ChatConfigurationModal
-        visible={visible}
-        showModal={showModal}
-        hideModal={hideModal}
-        id={currentDialog.id}
+        visible={dialogEditVisible}
+        initialDialog={initialDialog}
+        showModal={showDialogEditModal}
+        hideModal={hideDialogEditModal}
+        loading={dialogSettingLoading}
+        onOk={onDialogEditOk}
+        clearDialog={clearDialog}
       ></ChatConfigurationModal>
       <RenameModal
         visible={conversationRenameVisible}
diff --git a/web/src/pages/chat/model.ts b/web/src/pages/chat/model.ts
index f8950b3..22bf7b4 100644
--- a/web/src/pages/chat/model.ts
+++ b/web/src/pages/chat/model.ts
@@ -63,16 +63,21 @@ const model: DvaModel<ChatModelState> = {
 
   effects: {
     *getDialog({ payload }, { call, put }) {
-      const { data } = yield call(chatService.getDialog, payload);
-      if (data.retcode === 0) {
+      const needToBeSaved =
+        payload.needToBeSaved === undefined ? true : payload.needToBeSaved;
+      const { data } = yield call(chatService.getDialog, {
+        dialog_id: payload.dialog_id,
+      });
+      if (data.retcode === 0 && needToBeSaved) {
         yield put({ type: 'setCurrentDialog', payload: data.data });
       }
+      return data;
     },
     *setDialog({ payload }, { call, put }) {
       const { data } = yield call(chatService.setDialog, payload);
       if (data.retcode === 0) {
         yield put({ type: 'listDialog' });
-        message.success('Created successfully !');
+        message.success(payload.dialog_id ? 'Modified!' : 'Created!');
       }
       return data.retcode;
     },
diff --git a/web/src/utils/history.ts b/web/src/utils/history.ts
deleted file mode 100644
index f529e5d..0000000
--- a/web/src/utils/history.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { createBrowserHistory } from 'history';
-
-export const history = createBrowserHistory();
diff --git a/web/src/utils/request.ts b/web/src/utils/request.ts
index fb16464..adc77c5 100644
--- a/web/src/utils/request.ts
+++ b/web/src/utils/request.ts
@@ -1,11 +1,8 @@
-import { message, notification } from 'antd';
-import { RequestMethod, extend } from 'umi-request';
-
 import { Authorization } from '@/constants/authorization';
-import api from '@/utils/api';
 import authorizationUtil from '@/utils/authorizationUtil';
-
-const { login } = api;
+import { message, notification } from 'antd';
+import { history } from 'umi';
+import { RequestMethod, extend } from 'umi-request';
 
 const ABORT_REQUEST_ERR_MESSAGE = 'The user aborted a request.'; // 手动中断请求。errorHandler 抛出的error message
 
@@ -120,7 +117,7 @@ request.interceptors.response.use(async (response: any, options) => {
       duration: 3,
     });
     authorizationUtil.removeAll();
-    // history.push('/login'); // Will not jump to the login page
+    history.push('/login'); // Will not jump to the login page
   } else if (data.retcode !== 0) {
     if (data.retcode === 100) {
       message.error(data.retmsg);
-- 
GitLab