From 5a0f1d2b844510c8f41001e1bb96de4f78126587 Mon Sep 17 00:00:00 2001
From: balibabu <cike8899@users.noreply.github.com>
Date: Thu, 22 Feb 2024 17:14:25 +0800
Subject: [PATCH] feat: fetch conversation and delete chat dialog (#69)

* feat: set chat configuration to backend

* feat: exclude unEnabled variables

* feat: delete chat dialog

* feat: fetch conversation
---
 web/src/constants/chat.ts                     |   4 +
 web/src/hooks/commonHooks.ts                  |  34 ++
 web/src/hooks/knowledgeHook.ts                |  11 +-
 web/src/interfaces/database/chat.ts           |  19 +
 .../parsing-status-cell/index.tsx             |   4 +-
 .../assistant-setting.tsx                     |   6 +-
 .../chat/chat-configuration-modal/index.tsx   |  49 ++-
 .../chat-configuration-modal/interface.ts     |  14 -
 .../model-setting.tsx                         |   4 +-
 .../prompt-engine.tsx                         |  32 +-
 web/src/pages/chat/chat-container/index.less  |  15 +
 web/src/pages/chat/chat-container/index.tsx   |  36 +-
 .../constants.ts                              |   7 +
 web/src/pages/chat/hooks.ts                   | 342 +++++++++++++++++-
 web/src/pages/chat/index.less                 |  23 ++
 web/src/pages/chat/index.tsx                  | 164 ++++++---
 web/src/pages/chat/interface.ts               |  31 ++
 web/src/pages/chat/model.ts                   |  83 ++++-
 web/src/pages/chat/utils.ts                   |  12 +
 web/src/pages/knowledge/model.ts              |   2 +-
 web/src/services/chatService.ts               |   5 +
 web/src/utils/api.ts                          |   1 +
 22 files changed, 783 insertions(+), 115 deletions(-)
 create mode 100644 web/src/constants/chat.ts
 create mode 100644 web/src/hooks/commonHooks.ts
 delete mode 100644 web/src/pages/chat/chat-configuration-modal/interface.ts
 rename web/src/pages/chat/{chat-configuration-modal => }/constants.ts (62%)
 create mode 100644 web/src/pages/chat/interface.ts
 create mode 100644 web/src/pages/chat/utils.ts

diff --git a/web/src/constants/chat.ts b/web/src/constants/chat.ts
new file mode 100644
index 0000000..2ce95b5
--- /dev/null
+++ b/web/src/constants/chat.ts
@@ -0,0 +1,4 @@
+export enum MessageType {
+  Assistant = 'assistant',
+  User = 'user',
+}
diff --git a/web/src/hooks/commonHooks.ts b/web/src/hooks/commonHooks.ts
new file mode 100644
index 0000000..e4361cd
--- /dev/null
+++ b/web/src/hooks/commonHooks.ts
@@ -0,0 +1,34 @@
+import isEqual from 'lodash/isEqual';
+import { useEffect, useRef, useState } from 'react';
+
+export const useSetModalState = () => {
+  const [visible, setVisible] = useState(false);
+
+  const showModal = () => {
+    setVisible(true);
+  };
+  const hideModal = () => {
+    setVisible(false);
+  };
+
+  return { visible, showModal, hideModal };
+};
+
+export const useDeepCompareEffect = (
+  effect: React.EffectCallback,
+  deps: React.DependencyList,
+) => {
+  const ref = useRef<React.DependencyList>();
+  let callback: ReturnType<React.EffectCallback> = () => {};
+  if (!isEqual(deps, ref.current)) {
+    callback = effect();
+    ref.current = deps;
+  }
+  useEffect(() => {
+    return () => {
+      if (callback) {
+        callback();
+      }
+    };
+  }, []);
+};
diff --git a/web/src/hooks/knowledgeHook.ts b/web/src/hooks/knowledgeHook.ts
index ba090d9..9ad41f5 100644
--- a/web/src/hooks/knowledgeHook.ts
+++ b/web/src/hooks/knowledgeHook.ts
@@ -125,11 +125,18 @@ export const useFetchKnowledgeBaseConfiguration = () => {
   }, [fetchKnowledgeBaseConfiguration]);
 };
 
-export const useFetchKnowledgeList = (): IKnowledge[] => {
+export const useFetchKnowledgeList = (
+  shouldFilterListWithoutDocument: boolean = false,
+): IKnowledge[] => {
   const dispatch = useDispatch();
 
   const knowledgeModel = useSelector((state: any) => state.knowledgeModel);
   const { data = [] } = knowledgeModel;
+  const list = useMemo(() => {
+    return shouldFilterListWithoutDocument
+      ? data.filter((x: IKnowledge) => x.doc_num > 0)
+      : data;
+  }, [data, shouldFilterListWithoutDocument]);
 
   const fetchList = useCallback(() => {
     dispatch({
@@ -141,5 +148,5 @@ export const useFetchKnowledgeList = (): IKnowledge[] => {
     fetchList();
   }, [fetchList]);
 
-  return data;
+  return list;
 };
diff --git a/web/src/interfaces/database/chat.ts b/web/src/interfaces/database/chat.ts
index 6aec10a..f7c4a23 100644
--- a/web/src/interfaces/database/chat.ts
+++ b/web/src/interfaces/database/chat.ts
@@ -1,3 +1,5 @@
+import { MessageType } from '@/constants/chat';
+
 export interface PromptConfig {
   empty_response: string;
   parameters: Parameter[];
@@ -45,3 +47,20 @@ export interface IDialog {
   update_date: string;
   update_time: number;
 }
+
+export interface IConversation {
+  create_date: string;
+  create_time: number;
+  dialog_id: string;
+  id: string;
+  message: Message[];
+  reference: any[];
+  name: string;
+  update_date: string;
+  update_time: number;
+}
+
+export interface Message {
+  content: string;
+  role: MessageType;
+}
diff --git a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx
index 5c0711a..22f840b 100644
--- a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx
+++ b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx
@@ -75,9 +75,7 @@ export const ParsingStatusCell = ({ record }: IProps) => {
 
   return (
     <Flex justify={'space-between'}>
-      <Popover
-        content={isRunning && <PopoverContent record={record}></PopoverContent>}
-      >
+      <Popover content={<PopoverContent record={record}></PopoverContent>}>
         <Tag color={runningStatus.color}>
           {isRunning ? (
             <Space>
diff --git a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
index 968d277..0ce8af7 100644
--- a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
+++ b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
@@ -1,15 +1,13 @@
 import { Form, Input, Select } from 'antd';
 
 import classNames from 'classnames';
-import { ISegmentedContentProps } from './interface';
+import { ISegmentedContentProps } from '../interface';
 
 import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
 import styles from './index.less';
 
-const { Option } = Select;
-
 const AssistantSetting = ({ show }: ISegmentedContentProps) => {
-  const knowledgeList = useFetchKnowledgeList();
+  const knowledgeList = useFetchKnowledgeList(true);
   const knowledgeOptions = knowledgeList.map((x) => ({
     label: x.name,
     value: x.id,
diff --git a/web/src/pages/chat/chat-configuration-modal/index.tsx b/web/src/pages/chat/chat-configuration-modal/index.tsx
index 64943c9..3978961 100644
--- a/web/src/pages/chat/chat-configuration-modal/index.tsx
+++ b/web/src/pages/chat/chat-configuration-modal/index.tsx
@@ -3,13 +3,16 @@ import { IModalManagerChildrenProps } from '@/components/modal-manager';
 import { Divider, Flex, Form, Modal, Segmented } from 'antd';
 import { SegmentedValue } from 'antd/es/segmented';
 import omit from 'lodash/omit';
-import { useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import AssistantSetting from './assistant-setting';
 import ModelSetting from './model-setting';
 import PromptEngine from './prompt-engine';
 
-import { useSetDialog } from '../hooks';
-import { variableEnabledFieldMap } from './constants';
+import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
+import { variableEnabledFieldMap } from '../constants';
+import { useFetchDialog, useResetCurrentDialog, useSetDialog } from '../hooks';
+import { IPromptConfigParameters } from '../interface';
+import { excludeUnEnabledVariables } from '../utils';
 import styles from './index.less';
 
 enum ConfigurationSegmented {
@@ -40,32 +43,46 @@ const validateMessages = {
   },
 };
 
-const ChatConfigurationModal = ({
-  visible,
-  hideModal,
-}: IModalManagerChildrenProps) => {
+interface IProps extends IModalManagerChildrenProps {
+  id: string;
+}
+
+const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => {
   const [form] = Form.useForm();
   const [value, setValue] = useState<ConfigurationSegmented>(
     ConfigurationSegmented.AssistantSetting,
   );
-  const promptEngineRef = useRef(null);
+  const promptEngineRef = useRef<Array<IPromptConfigParameters>>([]);
+  const loading = useOneNamespaceEffectsLoading('chatModel', ['setDialog']);
 
   const setDialog = useSetDialog();
+  const currentDialog = useFetchDialog(id, visible);
+  const { resetCurrentDialog } = useResetCurrentDialog();
 
   const handleOk = async () => {
     const values = await form.validateFields();
-    const nextValues: any = omit(values, Object.keys(variableEnabledFieldMap));
+    const nextValues: any = omit(values, [
+      ...Object.keys(variableEnabledFieldMap),
+      'parameters',
+      ...excludeUnEnabledVariables(values),
+    ]);
+    const emptyResponse = nextValues.prompt_config?.empty_response ?? '';
     const finalValues = {
+      dialog_id: id,
       ...nextValues,
       prompt_config: {
         ...nextValues.prompt_config,
         parameters: promptEngineRef.current,
+        empty_response: emptyResponse,
       },
     };
     console.info(promptEngineRef.current);
     console.info(nextValues);
     console.info(finalValues);
-    setDialog(finalValues);
+    const retcode: number = await setDialog(finalValues);
+    if (retcode === 0) {
+      hideModal();
+    }
   };
 
   const handleCancel = () => {
@@ -76,6 +93,11 @@ const ChatConfigurationModal = ({
     setValue(val as ConfigurationSegmented);
   };
 
+  const handleModalAfterClose = () => {
+    resetCurrentDialog();
+    form.resetFields();
+  };
+
   const title = (
     <Flex gap={16}>
       <ChatConfigurationAtom></ChatConfigurationAtom>
@@ -89,6 +111,10 @@ const ChatConfigurationModal = ({
     </Flex>
   );
 
+  useEffect(() => {
+    form.setFieldsValue(currentDialog);
+  }, [currentDialog, form]);
+
   return (
     <Modal
       title={title}
@@ -96,6 +122,9 @@ const ChatConfigurationModal = ({
       open={visible}
       onOk={handleOk}
       onCancel={handleCancel}
+      confirmLoading={loading}
+      destroyOnClose
+      afterClose={handleModalAfterClose}
     >
       <Segmented
         size={'large'}
diff --git a/web/src/pages/chat/chat-configuration-modal/interface.ts b/web/src/pages/chat/chat-configuration-modal/interface.ts
deleted file mode 100644
index 55b67c3..0000000
--- a/web/src/pages/chat/chat-configuration-modal/interface.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { FormInstance } from 'antd';
-
-export interface ISegmentedContentProps {
-  show: boolean;
-  form: FormInstance;
-}
-
-export interface IVariable {
-  temperature: number;
-  top_p: number;
-  frequency_penalty: number;
-  presence_penalty: number;
-  max_tokens: number;
-}
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 1c421fc..5f68a6d 100644
--- a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx
+++ b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx
@@ -6,10 +6,10 @@ import {
 import { Divider, Flex, Form, InputNumber, Select, Slider, Switch } from 'antd';
 import classNames from 'classnames';
 import { useEffect } from 'react';
-import { ISegmentedContentProps } from './interface';
+import { ISegmentedContentProps } from '../interface';
 
 import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks';
-import { variableEnabledFieldMap } from './constants';
+import { variableEnabledFieldMap } from '../constants';
 import styles from './index.less';
 
 const ModelSetting = ({ show, form }: ISegmentedContentProps) => {
diff --git a/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx
index 3eaedfa..f6bd1c5 100644
--- a/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx
+++ b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx
@@ -21,17 +21,16 @@ import {
   useState,
 } from 'react';
 import { v4 as uuid } from 'uuid';
+import {
+  VariableTableDataType as DataType,
+  IPromptConfigParameters,
+  ISegmentedContentProps,
+} from '../interface';
 import { EditableCell, EditableRow } from './editable-cell';
-import { ISegmentedContentProps } from './interface';
 
+import { useSelectPromptConfigParameters } from '../hooks';
 import styles from './index.less';
 
-interface DataType {
-  key: string;
-  variable: string;
-  optional: boolean;
-}
-
 type FieldType = {
   similarity_threshold?: number;
   vector_similarity_weight?: number;
@@ -39,10 +38,11 @@ type FieldType = {
 };
 
 const PromptEngine = (
-  { show, form }: ISegmentedContentProps,
-  ref: ForwardedRef<Array<Omit<DataType, 'variable'>>>,
+  { show }: ISegmentedContentProps,
+  ref: ForwardedRef<Array<IPromptConfigParameters>>,
 ) => {
   const [dataSource, setDataSource] = useState<DataType[]>([]);
+  const parameters = useSelectPromptConfigParameters();
 
   const components = {
     body: {
@@ -99,12 +99,6 @@ const PromptEngine = (
     [dataSource],
   );
 
-  useEffect(() => {
-    form.setFieldValue(['prompt_config', 'parameters'], dataSource);
-    const x = form.getFieldValue(['prompt_config', 'parameters']);
-    console.info(x);
-  }, [dataSource, form]);
-
   const columns: TableProps<DataType>['columns'] = [
     {
       title: 'key',
@@ -146,6 +140,10 @@ const PromptEngine = (
     },
   ];
 
+  useEffect(() => {
+    setDataSource(parameters);
+  }, [parameters]);
+
   return (
     <section
       className={classNames({
@@ -153,7 +151,7 @@ const PromptEngine = (
       })}
     >
       <Form.Item
-        label="Orchestrate"
+        label="System"
         rules={[{ required: true, message: 'Please input!' }]}
         name={['prompt_config', 'system']}
         initialValue={`你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。
@@ -161,7 +159,7 @@ const PromptEngine = (
         {knowledge}
         以上是知识库。`}
       >
-        <Input.TextArea autoSize={{ maxRows: 5, minRows: 5 }} />
+        <Input.TextArea autoSize={{ maxRows: 8, minRows: 5 }} />
       </Form.Item>
       <Divider></Divider>
       <SimilaritySlider></SimilaritySlider>
diff --git a/web/src/pages/chat/chat-container/index.less b/web/src/pages/chat/chat-container/index.less
index 147213c..ff6eec7 100644
--- a/web/src/pages/chat/chat-container/index.less
+++ b/web/src/pages/chat/chat-container/index.less
@@ -1,3 +1,18 @@
 .chatContainer {
   padding: 0 24px 24px;
 }
+
+.messageItem {
+  .messageItemContent {
+    display: inline-block;
+    width: 300px;
+  }
+}
+
+.messageItemLeft {
+  text-align: left;
+}
+
+.messageItemRight {
+  text-align: right;
+}
diff --git a/web/src/pages/chat/chat-container/index.tsx b/web/src/pages/chat/chat-container/index.tsx
index bd8d837..b58b52f 100644
--- a/web/src/pages/chat/chat-container/index.tsx
+++ b/web/src/pages/chat/chat-container/index.tsx
@@ -1,13 +1,41 @@
-import { Button, Flex, Input } from 'antd';
+import { Button, Flex, Input, Typography } from 'antd';
 import { ChangeEventHandler, useState } from 'react';
 
+import { Message } from '@/interfaces/database/chat';
+import classNames from 'classnames';
+import { useFetchConversation, useSendMessage } from '../hooks';
+
+import { MessageType } from '@/constants/chat';
+import { IClientConversation } from '../interface';
 import styles from './index.less';
 
+const { Paragraph } = Typography;
+
+const MessageItem = ({ item }: { item: Message }) => {
+  return (
+    <div
+      className={classNames(styles.messageItem, {
+        [styles.messageItemLeft]: item.role === MessageType.Assistant,
+        [styles.messageItemRight]: item.role === MessageType.User,
+      })}
+    >
+      <span className={styles.messageItemContent}>
+        <Paragraph ellipsis={{ tooltip: item.content, rows: 3 }}>
+          {item.content}
+        </Paragraph>
+      </span>
+    </div>
+  );
+};
+
 const ChatContainer = () => {
   const [value, setValue] = useState('');
+  const conversation: IClientConversation = useFetchConversation();
+  const { sendMessage } = useSendMessage();
 
   const handlePressEnter = () => {
     console.info(value);
+    sendMessage(value);
   };
 
   const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
@@ -16,7 +44,11 @@ const ChatContainer = () => {
 
   return (
     <Flex flex={1} className={styles.chatContainer} vertical>
-      <Flex flex={1}>xx</Flex>
+      <Flex flex={1} vertical>
+        {conversation?.message?.map((message) => (
+          <MessageItem key={message.id} item={message}></MessageItem>
+        ))}
+      </Flex>
       <Input
         size="large"
         placeholder="Message Resume Assistant..."
diff --git a/web/src/pages/chat/chat-configuration-modal/constants.ts b/web/src/pages/chat/constants.ts
similarity index 62%
rename from web/src/pages/chat/chat-configuration-modal/constants.ts
rename to web/src/pages/chat/constants.ts
index 640ad6f..4c2c5ed 100644
--- a/web/src/pages/chat/chat-configuration-modal/constants.ts
+++ b/web/src/pages/chat/constants.ts
@@ -5,3 +5,10 @@ export const variableEnabledFieldMap = {
   frequencyPenaltyEnabled: 'frequency_penalty',
   maxTokensEnabled: 'max_tokens',
 };
+
+export enum ChatSearchParams {
+  DialogId = 'dialogId',
+  ConversationId = 'conversationId',
+}
+
+export const EmptyConversationId = 'empty';
diff --git a/web/src/pages/chat/hooks.ts b/web/src/pages/chat/hooks.ts
index 588f63a..dd8e04a 100644
--- a/web/src/pages/chat/hooks.ts
+++ b/web/src/pages/chat/hooks.ts
@@ -1,6 +1,16 @@
+import showDeleteConfirm from '@/components/deleting-confirm';
+import { MessageType } from '@/constants/chat';
 import { IDialog } from '@/interfaces/database/chat';
-import { useCallback, useEffect } from 'react';
-import { useDispatch, useSelector } from 'umi';
+import omit from 'lodash/omit';
+import { useCallback, useEffect, useMemo } from 'react';
+import { useDispatch, useSearchParams, useSelector } from 'umi';
+import { v4 as uuid } from 'uuid';
+import { ChatSearchParams, EmptyConversationId } from './constants';
+import {
+  IClientConversation,
+  IMessage,
+  VariableTableDataType,
+} from './interface';
 
 export const useFetchDialogList = () => {
   const dispatch = useDispatch();
@@ -20,10 +30,336 @@ export const useSetDialog = () => {
 
   const setDialog = useCallback(
     (payload: IDialog) => {
-      dispatch({ type: 'chatModel/setDialog', payload });
+      return dispatch<any>({ type: 'chatModel/setDialog', payload });
     },
     [dispatch],
   );
 
   return setDialog;
 };
+
+export const useFetchDialog = (dialogId: string, visible: boolean): IDialog => {
+  const dispatch = useDispatch();
+  const currentDialog: IDialog = useSelector(
+    (state: any) => state.chatModel.currentDialog,
+  );
+
+  const fetchDialog = useCallback(() => {
+    if (dialogId) {
+      dispatch({
+        type: 'chatModel/getDialog',
+        payload: { dialog_id: dialogId },
+      });
+    }
+  }, [dispatch, dialogId]);
+
+  useEffect(() => {
+    if (dialogId && visible) {
+      fetchDialog();
+    }
+  }, [dialogId, fetchDialog, visible]);
+
+  return currentDialog;
+};
+
+export const useSetCurrentDialog = () => {
+  const dispatch = useDispatch();
+
+  const currentDialog: IDialog = useSelector(
+    (state: any) => state.chatModel.currentDialog,
+  );
+
+  const setCurrentDialog = useCallback(
+    (dialogId: string) => {
+      if (dialogId) {
+        dispatch({
+          type: 'chatModel/setCurrentDialog',
+          payload: { id: dialogId },
+        });
+      }
+    },
+    [dispatch],
+  );
+
+  return { currentDialog, setCurrentDialog };
+};
+
+export const useResetCurrentDialog = () => {
+  const dispatch = useDispatch();
+
+  const resetCurrentDialog = useCallback(() => {
+    dispatch({
+      type: 'chatModel/setCurrentDialog',
+      payload: {},
+    });
+  }, [dispatch]);
+
+  return { resetCurrentDialog };
+};
+
+export const useSelectPromptConfigParameters = (): VariableTableDataType[] => {
+  const currentDialog: IDialog = useSelector(
+    (state: any) => state.chatModel.currentDialog,
+  );
+
+  const finalParameters: VariableTableDataType[] = useMemo(() => {
+    const parameters = currentDialog?.prompt_config?.parameters ?? [];
+    if (!currentDialog.id) {
+      // The newly created chat has a default parameter
+      return [{ key: uuid(), variable: 'knowledge', optional: false }];
+    }
+    return parameters.map((x) => ({
+      key: uuid(),
+      variable: x.key,
+      optional: x.optional,
+    }));
+  }, [currentDialog]);
+
+  return finalParameters;
+};
+
+export const useRemoveDialog = () => {
+  const dispatch = useDispatch();
+
+  const removeDocument = (dialogIds: Array<string>) => () => {
+    return dispatch({
+      type: 'chatModel/removeDialog',
+      payload: {
+        dialog_ids: dialogIds,
+      },
+    });
+  };
+
+  const onRemoveDialog = (dialogIds: Array<string>) => {
+    showDeleteConfirm({ onOk: removeDocument(dialogIds) });
+  };
+
+  return { onRemoveDialog };
+};
+
+export const useClickDialogCard = () => {
+  const [currentQueryParameters, setSearchParams] = useSearchParams();
+
+  const newQueryParameters: URLSearchParams = useMemo(() => {
+    return new URLSearchParams(currentQueryParameters.toString());
+  }, [currentQueryParameters]);
+
+  const handleClickDialog = useCallback(
+    (dialogId: string) => {
+      newQueryParameters.set(ChatSearchParams.DialogId, dialogId);
+      setSearchParams(newQueryParameters);
+    },
+    [newQueryParameters, setSearchParams],
+  );
+
+  return { handleClickDialog };
+};
+
+export const useGetChatSearchParams = () => {
+  const [currentQueryParameters] = useSearchParams();
+
+  return {
+    dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '',
+    conversationId:
+      currentQueryParameters.get(ChatSearchParams.ConversationId) || '',
+  };
+};
+
+export const useSelectFirstDialogOnMount = () => {
+  const dialogList = useFetchDialogList();
+  const { dialogId } = useGetChatSearchParams();
+
+  const { handleClickDialog } = useClickDialogCard();
+
+  useEffect(() => {
+    if (dialogList.length > 0 && !dialogId) {
+      handleClickDialog(dialogList[0].id);
+    }
+  }, [dialogList, handleClickDialog, dialogId]);
+
+  return dialogList;
+};
+
+//#region conversation
+
+export const useFetchConversationList = (dialogId?: string) => {
+  const dispatch = useDispatch();
+  const conversationList: any[] = useSelector(
+    (state: any) => state.chatModel.conversationList,
+  );
+
+  const fetchConversationList = useCallback(() => {
+    if (dialogId) {
+      dispatch({
+        type: 'chatModel/listConversation',
+        payload: { dialog_id: dialogId },
+      });
+    }
+  }, [dispatch, dialogId]);
+
+  useEffect(() => {
+    fetchConversationList();
+  }, [fetchConversationList]);
+
+  return conversationList;
+};
+
+export const useClickConversationCard = () => {
+  const [currentQueryParameters, setSearchParams] = useSearchParams();
+  const newQueryParameters: URLSearchParams = new URLSearchParams(
+    currentQueryParameters.toString(),
+  );
+
+  const handleClickConversation = (conversationId: string) => {
+    newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
+    setSearchParams(newQueryParameters);
+  };
+
+  return { handleClickConversation };
+};
+
+export const useCreateTemporaryConversation = () => {
+  const dispatch = useDispatch();
+  const { dialogId } = useGetChatSearchParams();
+  const { handleClickConversation } = useClickConversationCard();
+  let chatModel = useSelector((state: any) => state.chatModel);
+  let currentConversation: Pick<
+    IClientConversation,
+    'id' | 'message' | 'name' | 'dialog_id'
+  > = chatModel.currentConversation;
+  let conversationList: IClientConversation[] = chatModel.conversationList;
+
+  const createTemporaryConversation = (message: string) => {
+    const messages = [...(currentConversation?.message ?? [])];
+    if (messages.some((x) => x.id === EmptyConversationId)) {
+      return;
+    }
+    messages.unshift({
+      id: EmptyConversationId,
+      content: message,
+      role: MessageType.Assistant,
+    });
+
+    // It’s the back-end data.
+    if ('id' in currentConversation) {
+      currentConversation = { ...currentConversation, message: messages };
+    } else {
+      // client data
+      currentConversation = {
+        id: EmptyConversationId,
+        name: 'New conversation',
+        dialog_id: dialogId,
+        message: messages,
+      };
+    }
+
+    const nextConversationList = [...conversationList];
+
+    nextConversationList.push(currentConversation as IClientConversation);
+
+    dispatch({
+      type: 'chatModel/setCurrentConversation',
+      payload: currentConversation,
+    });
+
+    dispatch({
+      type: 'chatModel/setConversationList',
+      payload: nextConversationList,
+    });
+    handleClickConversation(EmptyConversationId);
+  };
+
+  return { createTemporaryConversation };
+};
+
+export const useSetConversation = () => {
+  const dispatch = useDispatch();
+  const { dialogId } = useGetChatSearchParams();
+
+  const setConversation = (message: string) => {
+    return dispatch<any>({
+      type: 'chatModel/setConversation',
+      payload: {
+        // conversation_id: '',
+        dialog_id: dialogId,
+        name: message,
+        message: [
+          {
+            role: MessageType.Assistant,
+            content: message,
+          },
+        ],
+      },
+    });
+  };
+
+  return { setConversation };
+};
+
+export const useFetchConversation = () => {
+  const dispatch = useDispatch();
+  const { conversationId } = useGetChatSearchParams();
+  const conversation = useSelector(
+    (state: any) => state.chatModel.currentConversation,
+  );
+
+  const fetchConversation = useCallback(() => {
+    if (conversationId !== EmptyConversationId && conversationId !== '') {
+      dispatch({
+        type: 'chatModel/getConversation',
+        payload: {
+          conversation_id: conversationId,
+        },
+      });
+    }
+  }, [dispatch, conversationId]);
+
+  useEffect(() => {
+    fetchConversation();
+  }, [fetchConversation]);
+
+  return conversation;
+};
+
+export const useSendMessage = () => {
+  const dispatch = useDispatch();
+  const { setConversation } = useSetConversation();
+  const { conversationId } = useGetChatSearchParams();
+  const conversation = useSelector(
+    (state: any) => state.chatModel.currentConversation,
+  );
+  const { handleClickConversation } = useClickConversationCard();
+
+  const sendMessage = (message: string, id?: string) => {
+    dispatch({
+      type: 'chatModel/completeConversation',
+      payload: {
+        conversation_id: id ?? conversationId,
+        messages: [
+          ...(conversation?.message ?? []).map((x: IMessage) => omit(x, 'id')),
+          {
+            role: MessageType.User,
+            content: message,
+          },
+        ],
+      },
+    });
+  };
+
+  const handleSendMessage = async (message: string) => {
+    if (conversationId !== EmptyConversationId) {
+      sendMessage(message);
+    } else {
+      const data = await setConversation(message);
+      if (data.retcode === 0) {
+        const id = data.data.id;
+        handleClickConversation(id);
+        sendMessage(message, id);
+      }
+    }
+  };
+
+  return { sendMessage: handleSendMessage };
+};
+
+//#endregion
diff --git a/web/src/pages/chat/index.less b/web/src/pages/chat/index.less
index 4400e7e..e3b5f89 100644
--- a/web/src/pages/chat/index.less
+++ b/web/src/pages/chat/index.less
@@ -5,6 +5,10 @@
     width: 288px;
     padding: 26px;
 
+    .chatAppContent {
+      overflow-y: auto;
+    }
+
     .chatAppCard {
       :global(.ant-card-body) {
         padding: 10px;
@@ -15,6 +19,12 @@
         }
       }
     }
+    .chatAppCardSelected {
+      :global(.ant-card-body) {
+        background-color: @gray11;
+        border-radius: 8px;
+      }
+    }
   }
   .chatTitleWrapper {
     width: 220px;
@@ -29,6 +39,19 @@
     padding: 5px 10px;
   }
 
+  .chatTitleCard {
+    :global(.ant-card-body) {
+      padding: 8px;
+    }
+  }
+
+  .chatTitleCardSelected {
+    :global(.ant-card-body) {
+      background-color: @gray11;
+      border-radius: 8px;
+    }
+  }
+
   .divider {
     margin: 0;
     height: 100%;
diff --git a/web/src/pages/chat/index.tsx b/web/src/pages/chat/index.tsx
index f33bbd3..33b20e4 100644
--- a/web/src/pages/chat/index.tsx
+++ b/web/src/pages/chat/index.tsx
@@ -1,3 +1,5 @@
+import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg';
+import { useSetModalState } from '@/hooks/commonHooks';
 import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons';
 import {
   Button,
@@ -9,20 +11,39 @@ import {
   Space,
   Tag,
 } from 'antd';
-import ChatContainer from './chat-container';
-
-import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg';
-import ModalManager from '@/components/modal-manager';
 import classNames from 'classnames';
+import { useCallback, useState } from 'react';
 import ChatConfigurationModal from './chat-configuration-modal';
-import { useFetchDialogList } from './hooks';
+import ChatContainer from './chat-container';
+import {
+  useClickConversationCard,
+  useClickDialogCard,
+  useCreateTemporaryConversation,
+  useFetchConversationList,
+  useFetchDialog,
+  useGetChatSearchParams,
+  useRemoveDialog,
+  useSelectFirstDialogOnMount,
+  useSetCurrentDialog,
+} from './hooks';
 
-import { useState } from 'react';
 import styles from './index.less';
 
 const Chat = () => {
-  const dialogList = useFetchDialogList();
+  const dialogList = useSelectFirstDialogOnMount();
   const [activated, setActivated] = useState<string>('');
+  const { visible, hideModal, showModal } = useSetModalState();
+  const { setCurrentDialog, currentDialog } = useSetCurrentDialog();
+  const { onRemoveDialog } = useRemoveDialog();
+  const { handleClickDialog } = useClickDialogCard();
+  const { handleClickConversation } = useClickConversationCard();
+  const { dialogId, conversationId } = useGetChatSearchParams();
+  const list = useFetchConversationList(dialogId);
+  const { createTemporaryConversation } = useCreateTemporaryConversation();
+
+  const selectedDialog = useFetchDialog(dialogId, true);
+
+  const prologue = selectedDialog?.prompt_config?.prologue || '';
 
   const handleAppCardEnter = (id: string) => () => {
     setActivated(id);
@@ -32,72 +53,84 @@ const Chat = () => {
     setActivated('');
   };
 
-  const items: MenuProps['items'] = [
-    {
-      key: '1',
-      label: (
-        <a
-          target="_blank"
-          rel="noopener noreferrer"
-          href="https://www.antgroup.com"
-        >
-          1st menu item
-        </a>
-      ),
-    },
-  ];
+  const handleShowChatConfigurationModal = (dialogId?: string) => () => {
+    if (dialogId) {
+      setCurrentDialog(dialogId);
+    }
+    showModal();
+  };
+
+  const handleDialogCardClick = (dialogId: string) => () => {
+    handleClickDialog(dialogId);
+  };
+
+  const handleConversationCardClick = (dialogId: string) => () => {
+    handleClickConversation(dialogId);
+  };
+
+  const handleCreateTemporaryConversation = useCallback(() => {
+    createTemporaryConversation(prologue);
+  }, [createTemporaryConversation, prologue]);
 
-  const appItems: MenuProps['items'] = [
+  const items: MenuProps['items'] = [
     {
       key: '1',
+      onClick: handleCreateTemporaryConversation,
       label: (
         <Space>
-          <EditOutlined />
-          Edit
-        </Space>
-      ),
-    },
-    { type: 'divider' },
-    {
-      key: '2',
-      label: (
-        <Space>
-          <DeleteOutlined />
-          Delete chat
+          <EditOutlined /> New chat
         </Space>
       ),
     },
   ];
 
+  const buildAppItems = (dialogId: string) => {
+    const appItems: MenuProps['items'] = [
+      {
+        key: '1',
+        onClick: handleShowChatConfigurationModal(dialogId),
+        label: (
+          <Space>
+            <EditOutlined />
+            Edit
+          </Space>
+        ),
+      },
+      { type: 'divider' },
+      {
+        key: '2',
+        onClick: () => onRemoveDialog([dialogId]),
+        label: (
+          <Space>
+            <DeleteOutlined />
+            Delete chat
+          </Space>
+        ),
+      },
+    ];
+
+    return appItems;
+  };
+
   return (
     <Flex className={styles.chatWrapper}>
       <Flex className={styles.chatAppWrapper}>
         <Flex flex={1} vertical>
-          <ModalManager>
-            {({ visible, showModal, hideModal }) => {
-              return (
-                <>
-                  <Button type="primary" onClick={() => showModal()}>
-                    Create an Assistant
-                  </Button>
-                  <ChatConfigurationModal
-                    visible={visible}
-                    showModal={showModal}
-                    hideModal={hideModal}
-                  ></ChatConfigurationModal>
-                </>
-              );
-            }}
-          </ModalManager>
-
+          <Button type="primary" onClick={handleShowChatConfigurationModal()}>
+            Create an Assistant
+          </Button>
           <Divider></Divider>
-          <Space direction={'vertical'} size={'middle'}>
+          <Flex className={styles.chatAppContent} vertical gap={10}>
             {dialogList.map((x) => (
               <Card
                 key={x.id}
-                className={classNames(styles.chatAppCard)}
+                hoverable
+                className={classNames(styles.chatAppCard, {
+                  [styles.chatAppCardSelected]: dialogId === x.id,
+                })}
                 onMouseEnter={handleAppCardEnter(x.id)}
                 onMouseLeave={handleAppCardLeave}
+                onClick={handleDialogCardClick(x.id)}
               >
                 <Flex justify="space-between" align="center">
                   <Space>
@@ -109,7 +142,7 @@ const Chat = () => {
                   </Space>
                   {activated === x.id && (
                     <section>
-                      <Dropdown menu={{ items: appItems }}>
+                      <Dropdown menu={{ items: buildAppItems(x.id) }}>
                         <ChatAppCube className={styles.cubeIcon}></ChatAppCube>
                       </Dropdown>
                     </section>
@@ -117,7 +150,7 @@ const Chat = () => {
                 </Flex>
               </Card>
             ))}
-          </Space>
+          </Flex>
         </Flex>
       </Flex>
       <Divider type={'vertical'} className={styles.divider}></Divider>
@@ -137,11 +170,30 @@ const Chat = () => {
             </Dropdown>
           </Flex>
           <Divider></Divider>
-          <section className={styles.chatTitleContent}>today</section>
+          <Flex vertical gap={10} className={styles.chatTitleContent}>
+            {list.map((x) => (
+              <Card
+                key={x.id}
+                hoverable
+                onClick={handleConversationCardClick(x.id)}
+                className={classNames(styles.chatTitleCard, {
+                  [styles.chatTitleCardSelected]: x.id === conversationId,
+                })}
+              >
+                <div>{x.name}</div>
+              </Card>
+            ))}
+          </Flex>
         </Flex>
       </Flex>
       <Divider type={'vertical'} className={styles.divider}></Divider>
       <ChatContainer></ChatContainer>
+      <ChatConfigurationModal
+        visible={visible}
+        showModal={showModal}
+        hideModal={hideModal}
+        id={currentDialog.id}
+      ></ChatConfigurationModal>
     </Flex>
   );
 };
diff --git a/web/src/pages/chat/interface.ts b/web/src/pages/chat/interface.ts
new file mode 100644
index 0000000..c45da81
--- /dev/null
+++ b/web/src/pages/chat/interface.ts
@@ -0,0 +1,31 @@
+import { IConversation, Message } from '@/interfaces/database/chat';
+import { FormInstance } from 'antd';
+
+export interface ISegmentedContentProps {
+  show: boolean;
+  form: FormInstance;
+}
+
+export interface IVariable {
+  temperature: number;
+  top_p: number;
+  frequency_penalty: number;
+  presence_penalty: number;
+  max_tokens: number;
+}
+
+export interface VariableTableDataType {
+  key: string;
+  variable: string;
+  optional: boolean;
+}
+
+export type IPromptConfigParameters = Omit<VariableTableDataType, 'variable'>;
+
+export interface IMessage extends Message {
+  id: string;
+}
+
+export interface IClientConversation extends IConversation {
+  message: IMessage[];
+}
diff --git a/web/src/pages/chat/model.ts b/web/src/pages/chat/model.ts
index dd5c5d3..ff7f4ff 100644
--- a/web/src/pages/chat/model.ts
+++ b/web/src/pages/chat/model.ts
@@ -1,11 +1,16 @@
-import { IDialog } from '@/interfaces/database/chat';
+import { IConversation, IDialog, Message } from '@/interfaces/database/chat';
 import chatService from '@/services/chatService';
 import { message } from 'antd';
 import { DvaModel } from 'umi';
+import { v4 as uuid } from 'uuid';
+import { IClientConversation, IMessage } from './interface';
 
 export interface ChatModelState {
   name: string;
   dialogList: IDialog[];
+  currentDialog: IDialog;
+  conversationList: IConversation[];
+  currentConversation: IClientConversation;
 }
 
 const model: DvaModel<ChatModelState> = {
@@ -13,6 +18,9 @@ const model: DvaModel<ChatModelState> = {
   state: {
     name: 'kate',
     dialogList: [],
+    currentDialog: <IDialog>{},
+    conversationList: [],
+    currentConversation: {} as IClientConversation,
   },
   reducers: {
     save(state, action) {
@@ -27,11 +35,50 @@ const model: DvaModel<ChatModelState> = {
         dialogList: payload,
       };
     },
+    setCurrentDialog(state, { payload }) {
+      return {
+        ...state,
+        currentDialog: payload,
+      };
+    },
+    setConversationList(state, { payload }) {
+      return {
+        ...state,
+        conversationList: payload,
+      };
+    },
+    setCurrentConversation(state, { payload }) {
+      const messageList = payload?.message.map((x: Message | IMessage) => ({
+        ...x,
+        id: 'id' in x ? x.id : uuid(),
+      }));
+      return {
+        ...state,
+        currentConversation: { ...payload, message: messageList },
+      };
+    },
+    addEmptyConversationToList(state, {}) {
+      const list = [...state.conversationList];
+      // if (list.every((x) => x.id !== 'empty')) {
+      //   list.push({
+      //     id: 'empty',
+      //     name: 'New conversation',
+      //     message: [],
+      //   });
+      // }
+      return {
+        ...state,
+        conversationList: list,
+      };
+    },
   },
 
   effects: {
     *getDialog({ payload }, { call, put }) {
       const { data } = yield call(chatService.getDialog, payload);
+      if (data.retcode === 0) {
+        yield put({ type: 'setCurrentDialog', payload: data.data });
+      }
     },
     *setDialog({ payload }, { call, put }) {
       const { data } = yield call(chatService.setDialog, payload);
@@ -39,6 +86,15 @@ const model: DvaModel<ChatModelState> = {
         yield put({ type: 'listDialog' });
         message.success('Created successfully !');
       }
+      return data.retcode;
+    },
+    *removeDialog({ payload }, { call, put }) {
+      const { data } = yield call(chatService.removeDialog, payload);
+      if (data.retcode === 0) {
+        yield put({ type: 'listDialog' });
+        message.success('Deleted successfully !');
+      }
+      return data.retcode;
     },
     *listDialog({ payload }, { call, put }) {
       const { data } = yield call(chatService.listDialog, payload);
@@ -46,15 +102,40 @@ const model: DvaModel<ChatModelState> = {
     },
     *listConversation({ payload }, { call, put }) {
       const { data } = yield call(chatService.listConversation, payload);
+      if (data.retcode === 0) {
+        yield put({ type: 'setConversationList', payload: data.data });
+      }
+      return data.retcode;
     },
     *getConversation({ payload }, { call, put }) {
       const { data } = yield call(chatService.getConversation, payload);
+      if (data.retcode === 0) {
+        yield put({ type: 'setCurrentConversation', payload: data.data });
+      }
+      return data.retcode;
     },
     *setConversation({ payload }, { call, put }) {
       const { data } = yield call(chatService.setConversation, payload);
+      if (data.retcode === 0) {
+        yield put({
+          type: 'listConversation',
+          payload: {
+            dialog_id: data.data.dialog_id,
+          },
+        });
+      }
+      return data;
     },
     *completeConversation({ payload }, { call, put }) {
       const { data } = yield call(chatService.completeConversation, payload);
+      if (data.retcode === 0) {
+        yield put({
+          type: 'getConversation',
+          payload: {
+            conversation_id: payload.conversation_id,
+          },
+        });
+      }
     },
   },
 };
diff --git a/web/src/pages/chat/utils.ts b/web/src/pages/chat/utils.ts
new file mode 100644
index 0000000..997dc37
--- /dev/null
+++ b/web/src/pages/chat/utils.ts
@@ -0,0 +1,12 @@
+import { variableEnabledFieldMap } from './constants';
+
+export const excludeUnEnabledVariables = (values: any) => {
+  const unEnabledFields: Array<keyof typeof variableEnabledFieldMap> =
+    Object.keys(variableEnabledFieldMap).filter((key) => !values[key]) as Array<
+      keyof typeof variableEnabledFieldMap
+    >;
+
+  return unEnabledFields.map(
+    (key) => `llm_setting.${variableEnabledFieldMap[key]}`,
+  );
+};
diff --git a/web/src/pages/knowledge/model.ts b/web/src/pages/knowledge/model.ts
index 51ab284..04357c9 100644
--- a/web/src/pages/knowledge/model.ts
+++ b/web/src/pages/knowledge/model.ts
@@ -31,7 +31,7 @@ const model: DvaModel<KnowledgeModelState> = {
     },
     *getList({ payload = {} }, { call, put }) {
       const { data } = yield call(kbService.getList, payload);
-      const { retcode, data: res, retmsg } = data;
+      const { retcode, data: res } = data;
 
       if (retcode === 0) {
         yield put({
diff --git a/web/src/services/chatService.ts b/web/src/services/chatService.ts
index 5fbaf1a..946de86 100644
--- a/web/src/services/chatService.ts
+++ b/web/src/services/chatService.ts
@@ -6,6 +6,7 @@ const {
   getDialog,
   setDialog,
   listDialog,
+  removeDialog,
   getConversation,
   setConversation,
   completeConversation,
@@ -21,6 +22,10 @@ const methods = {
     url: setDialog,
     method: 'post',
   },
+  removeDialog: {
+    url: removeDialog,
+    method: 'post',
+  },
   listDialog: {
     url: listDialog,
     method: 'get',
diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts
index f813071..777c413 100644
--- a/web/src/utils/api.ts
+++ b/web/src/utils/api.ts
@@ -45,6 +45,7 @@ export default {
 
   setDialog: `${api_host}/dialog/set`,
   getDialog: `${api_host}/dialog/get`,
+  removeDialog: `${api_host}/dialog/rm`,
   listDialog: `${api_host}/dialog/list`,
 
   setConversation: `${api_host}/conversation/set`,
-- 
GitLab