From 452020d33afa9591b1d29b6b4e8cec43c188f070 Mon Sep 17 00:00:00 2001
From: balibabu <cike8899@users.noreply.github.com>
Date: Mon, 19 Feb 2024 19:16:23 +0800
Subject: [PATCH] feat: add temperature and top-p to ModelSetting and add
 ChatConfigurationModal and  bind backend data to KnowledgeCard (#65)

* feat: bind backend data to KnowledgeCard

* feat: add ChatConfigurationModal

* feat: add temperature and top-p to ModelSetting
---
 web/package-lock.json                         |  81 ++++++++-
 web/package.json                              |   5 +-
 .../assets/svg/chat-configuration-atom.svg    |  24 +++
 web/src/layouts/components/header/index.less  |   4 +-
 .../knowledge-setting/configuration.tsx       |   2 +-
 .../assistant-setting.tsx                     |  35 ++++
 .../editable-cell.tsx                         | 103 +++++++++++
 .../chat/chat-configuration-modal/index.less  |  43 +++++
 .../chat/chat-configuration-modal/index.tsx   | 107 ++++++++++++
 .../chat-configuration-modal/interface.ts     |   3 +
 .../model-setting.tsx                         | 155 +++++++++++++++++
 .../prompt-engine.tsx                         | 163 ++++++++++++++++++
 web/src/pages/chat/chat-container/index.less  |   3 +
 web/src/pages/chat/chat-container/index.tsx   |  36 ++++
 web/src/pages/chat/index.less                 |  25 +++
 web/src/pages/chat/index.tsx                  |  58 ++++++-
 web/src/pages/chat/message-box.tsx            |  45 +++++
 .../pages/knowledge/knowledge-card/index.less |   2 +-
 .../pages/knowledge/knowledge-card/index.tsx  |   6 +-
 19 files changed, 890 insertions(+), 10 deletions(-)
 create mode 100644 web/src/assets/svg/chat-configuration-atom.svg
 create mode 100644 web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
 create mode 100644 web/src/pages/chat/chat-configuration-modal/editable-cell.tsx
 create mode 100644 web/src/pages/chat/chat-configuration-modal/index.less
 create mode 100644 web/src/pages/chat/chat-configuration-modal/index.tsx
 create mode 100644 web/src/pages/chat/chat-configuration-modal/interface.ts
 create mode 100644 web/src/pages/chat/chat-configuration-modal/model-setting.tsx
 create mode 100644 web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx
 create mode 100644 web/src/pages/chat/chat-container/index.less
 create mode 100644 web/src/pages/chat/chat-container/index.tsx
 create mode 100644 web/src/pages/chat/index.less
 create mode 100644 web/src/pages/chat/message-box.tsx

diff --git a/web/package-lock.json b/web/package-lock.json
index 3286ade..04902e1 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -18,16 +18,19 @@
         "lodash": "^4.17.21",
         "moment": "^2.30.1",
         "rc-tween-one": "^3.0.6",
+        "react-chat-elements": "^12.0.13",
         "react-i18next": "^14.0.0",
         "react-infinite-scroll-component": "^6.1.0",
         "umi": "^4.0.90",
-        "umi-request": "^1.4.0"
+        "umi-request": "^1.4.0",
+        "uuid": "^9.0.1"
       },
       "devDependencies": {
         "@react-dev-inspector/umi4-plugin": "^2.0.1",
         "@types/lodash": "^4.14.202",
         "@types/react": "^18.0.33",
         "@types/react-dom": "^18.0.11",
+        "@types/uuid": "^9.0.8",
         "@umijs/lint": "^4.1.1",
         "@umijs/plugins": "^4.1.0",
         "cross-env": "^7.0.3",
@@ -2929,6 +2932,12 @@
       "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==",
       "dev": true
     },
+    "node_modules/@types/uuid": {
+      "version": "9.0.8",
+      "resolved": "https://registry.npmmirror.com/@types/uuid/-/uuid-9.0.8.tgz",
+      "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==",
+      "dev": true
+    },
     "node_modules/@types/yargs": {
       "version": "16.0.9",
       "resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-16.0.9.tgz",
@@ -10669,6 +10678,11 @@
         "node": ">=8.9.0"
       }
     },
+    "node_modules/loaders.css": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmmirror.com/loaders.css/-/loaders.css-0.1.2.tgz",
+      "integrity": "sha512-Rhowlq24ey1VOeor+3wYOt9+MjaxBOJm1u4KlQgNC3+0xJ0LS4wq4iG57D/BPzvuD/7HHDGQOWJ+81oR2EI9bQ=="
+    },
     "node_modules/local-pkg": {
       "version": "0.4.3",
       "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.3.tgz",
@@ -12666,6 +12680,15 @@
       "resolved": "https://registry.npmmirror.com/process-warning/-/process-warning-1.0.0.tgz",
       "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q=="
     },
+    "node_modules/progressbar.js": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/progressbar.js/-/progressbar.js-1.1.1.tgz",
+      "integrity": "sha512-FBsw3BKsUbb+hNeYfiP3xzvAAQrPi4DnGDw66bCmfuRCDLcslxyxv2GyYUdBSKFGSIBa73CUP5WMcl6F8AAXlw==",
+      "dependencies": {
+        "lodash.merge": "^4.6.2",
+        "shifty": "^2.8.3"
+      }
+    },
     "node_modules/prompts": {
       "version": "2.4.2",
       "resolved": "https://registry.npmmirror.com/prompts/-/prompts-2.4.2.tgz",
@@ -13446,6 +13469,22 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/react-chat-elements": {
+      "version": "12.0.13",
+      "resolved": "https://registry.npmmirror.com/react-chat-elements/-/react-chat-elements-12.0.13.tgz",
+      "integrity": "sha512-Vu5x8kW4LPu8onKfz5vsuDwZsDhoQmTBHJdqKAhVsi42PCQ8KOfzHiDp0fPUJlinJZ/MTJTm69UAchpys4iSTQ==",
+      "dependencies": {
+        "classnames": "^2.2.5",
+        "progressbar.js": "^1.1.0",
+        "react-icons": "^4.3.1",
+        "react-spinkit": "^3.0.0",
+        "timeago.js": "^4.0.2"
+      },
+      "peerDependencies": {
+        "react": "^18.2.0",
+        "react-dom": "18.2.0"
+      }
+    },
     "node_modules/react-dev-inspector": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/react-dev-inspector/-/react-dev-inspector-2.0.1.tgz",
@@ -13784,6 +13823,14 @@
         }
       }
     },
+    "node_modules/react-icons": {
+      "version": "4.12.0",
+      "resolved": "https://registry.npmmirror.com/react-icons/-/react-icons-4.12.0.tgz",
+      "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==",
+      "peerDependencies": {
+        "react": "*"
+      }
+    },
     "node_modules/react-infinite-scroll-component": {
       "version": "6.1.0",
       "resolved": "https://registry.npmmirror.com/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz",
@@ -13924,6 +13971,17 @@
         "react": ">=15"
       }
     },
+    "node_modules/react-spinkit": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/react-spinkit/-/react-spinkit-3.0.0.tgz",
+      "integrity": "sha512-RrfGRPjqxHQiy7quPqhjPynTu0zobgQaZu1QYBMpJJ6pCSRRRK16EZMaxdE6fLVYFRJWpX/eGATWLMoVFFT5uQ==",
+      "dependencies": {
+        "classnames": "^2.2.3",
+        "loaders.css": "^0.1.2",
+        "object-assign": "^4.1.0",
+        "prop-types": "^15.5.8"
+      }
+    },
     "node_modules/reactcss": {
       "version": "1.2.3",
       "resolved": "https://registry.npmmirror.com/reactcss/-/reactcss-1.2.3.tgz",
@@ -14633,6 +14691,14 @@
       "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
       "dev": true
     },
+    "node_modules/shifty": {
+      "version": "2.20.4",
+      "resolved": "https://registry.npmmirror.com/shifty/-/shifty-2.20.4.tgz",
+      "integrity": "sha512-4Y0qRkg8ME5XN8yGNAwmFOmsIURGFKT9UQfNL6DDJQErYtN5HsjyoBuJn41ZQfTkuu2rIbRMn9qazjKsDpO2TA==",
+      "optionalDependencies": {
+        "fsevents": "^2.3.2"
+      }
+    },
     "node_modules/side-channel": {
       "version": "1.0.4",
       "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.4.tgz",
@@ -15783,6 +15849,11 @@
         "node": ">=12.22"
       }
     },
+    "node_modules/timeago.js": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/timeago.js/-/timeago.js-4.0.2.tgz",
+      "integrity": "sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w=="
+    },
     "node_modules/timers-browserify": {
       "version": "2.0.12",
       "resolved": "https://registry.npmmirror.com/timers-browserify/-/timers-browserify-2.0.12.tgz",
@@ -16859,6 +16930,14 @@
       "resolved": "https://registry.npmmirror.com/utila/-/utila-0.4.0.tgz",
       "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA=="
     },
+    "node_modules/uuid": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz",
+      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
     "node_modules/v8-compile-cache": {
       "version": "2.4.0",
       "resolved": "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz",
diff --git a/web/package.json b/web/package.json
index 8301f3f..9fcffb1 100644
--- a/web/package.json
+++ b/web/package.json
@@ -22,16 +22,19 @@
     "lodash": "^4.17.21",
     "moment": "^2.30.1",
     "rc-tween-one": "^3.0.6",
+    "react-chat-elements": "^12.0.13",
     "react-i18next": "^14.0.0",
     "react-infinite-scroll-component": "^6.1.0",
     "umi": "^4.0.90",
-    "umi-request": "^1.4.0"
+    "umi-request": "^1.4.0",
+    "uuid": "^9.0.1"
   },
   "devDependencies": {
     "@react-dev-inspector/umi4-plugin": "^2.0.1",
     "@types/lodash": "^4.14.202",
     "@types/react": "^18.0.33",
     "@types/react-dom": "^18.0.11",
+    "@types/uuid": "^9.0.8",
     "@umijs/lint": "^4.1.1",
     "@umijs/plugins": "^4.1.0",
     "cross-env": "^7.0.3",
diff --git a/web/src/assets/svg/chat-configuration-atom.svg b/web/src/assets/svg/chat-configuration-atom.svg
new file mode 100644
index 0000000..47e57a2
--- /dev/null
+++ b/web/src/assets/svg/chat-configuration-atom.svg
@@ -0,0 +1,24 @@
+<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <g filter="url(#filter0_d_550_8026)">
+        <path
+            d="M2.5 11C2.5 5.75329 6.75329 1.5 12 1.5H40C45.2467 1.5 49.5 5.75329 49.5 11V39C49.5 44.2467 45.2467 48.5 40 48.5H12C6.75329 48.5 2.5 44.2467 2.5 39V11Z"
+            stroke="#EAECF0" shape-rendering="crispEdges" />
+        <path
+            d="M25.9995 24.9997H26.0095M29.535 28.5352C24.8488 33.2215 19.4669 35.4376 17.5142 33.4849C15.5616 31.5323 17.7777 26.1504 22.464 21.4641C27.1503 16.7778 32.5322 14.5618 34.4848 16.5144C36.4374 18.467 34.2213 23.8489 29.535 28.5352ZM29.535 21.4639C34.2213 26.1502 36.4374 31.5321 34.4848 33.4848C32.5321 35.4374 27.1502 33.2213 22.4639 28.535C17.7776 23.8487 15.5616 18.4668 17.5142 16.5142C19.4668 14.5616 24.8487 16.7776 29.535 21.4639ZM26.4995 24.9997C26.4995 25.2758 26.2757 25.4997 25.9995 25.4997C25.7234 25.4997 25.4995 25.2758 25.4995 24.9997C25.4995 24.7235 25.7234 24.4997 25.9995 24.4997C26.2757 24.4997 26.4995 24.7235 26.4995 24.9997Z"
+            stroke="#344054" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
+    </g>
+    <defs>
+        <filter id="filter0_d_550_8026" x="0" y="0" width="52" height="52" filterUnits="userSpaceOnUse"
+            color-interpolation-filters="sRGB">
+            <feFlood flood-opacity="0" result="BackgroundImageFix" />
+            <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
+                result="hardAlpha" />
+            <feOffset dy="1" />
+            <feGaussianBlur stdDeviation="1" />
+            <feComposite in2="hardAlpha" operator="out" />
+            <feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.0941176 0 0 0 0 0.156863 0 0 0 0.05 0" />
+            <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_550_8026" />
+            <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_550_8026" result="shape" />
+        </filter>
+    </defs>
+</svg>
\ No newline at end of file
diff --git a/web/src/layouts/components/header/index.less b/web/src/layouts/components/header/index.less
index f27b072..9ef620f 100644
--- a/web/src/layouts/components/header/index.less
+++ b/web/src/layouts/components/header/index.less
@@ -19,9 +19,9 @@
 .appName {
   vertical-align: middle;
   font-family: Inter;
-  font-size: 14px;
+  font-size: 16px;
   font-style: normal;
-  font-weight: 400;
+  font-weight: 600;
   line-height: 20px;
 }
 
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 6e71f8c..24cfac4 100644
--- a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx
+++ b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx
@@ -68,7 +68,7 @@ const Configuration = () => {
     const fileList = values.avatar;
     let avatar;
 
-    if (Array.isArray(fileList)) {
+    if (Array.isArray(fileList) && fileList.length > 0) {
       avatar = fileList[0].thumbUrl;
     }
 
diff --git a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
new file mode 100644
index 0000000..4623c6a
--- /dev/null
+++ b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
@@ -0,0 +1,35 @@
+import { Form, Input } from 'antd';
+
+import classNames from 'classnames';
+import { ISegmentedContentProps } from './interface';
+
+import styles from './index.less';
+
+const AssistantSetting = ({ show }: ISegmentedContentProps) => {
+  return (
+    <section
+      className={classNames({
+        [styles.segmentedHidden]: !show,
+      })}
+    >
+      <Form.Item
+        name={'name'}
+        label="Assistant name"
+        rules={[{ required: true }]}
+      >
+        <Input placeholder="e.g. Resume Jarvis" />
+      </Form.Item>
+      <Form.Item name={'avatar'} label="Assistant avatar">
+        <Input />
+      </Form.Item>
+      <Form.Item name={'keywords'} label="Keywords">
+        <Input.TextArea autoSize={{ minRows: 3 }} />
+      </Form.Item>
+      <Form.Item name={'opener'} label="Set an opener">
+        <Input.TextArea autoSize={{ minRows: 5 }} />
+      </Form.Item>
+    </section>
+  );
+};
+
+export default AssistantSetting;
diff --git a/web/src/pages/chat/chat-configuration-modal/editable-cell.tsx b/web/src/pages/chat/chat-configuration-modal/editable-cell.tsx
new file mode 100644
index 0000000..f8b65cf
--- /dev/null
+++ b/web/src/pages/chat/chat-configuration-modal/editable-cell.tsx
@@ -0,0 +1,103 @@
+import { Form, FormInstance, Input, InputRef } from 'antd';
+import React, { useContext, useEffect, useRef, useState } from 'react';
+
+const EditableContext = React.createContext<FormInstance<any> | null>(null);
+
+interface EditableRowProps {
+  index: number;
+}
+
+interface Item {
+  key: string;
+  name: string;
+  age: string;
+  address: string;
+}
+
+export const EditableRow: React.FC<EditableRowProps> = ({
+  index,
+  ...props
+}) => {
+  const [form] = Form.useForm();
+  return (
+    <Form form={form} component={false}>
+      <EditableContext.Provider value={form}>
+        <tr {...props} />
+      </EditableContext.Provider>
+    </Form>
+  );
+};
+
+interface EditableCellProps {
+  title: React.ReactNode;
+  editable: boolean;
+  children: React.ReactNode;
+  dataIndex: keyof Item;
+  record: Item;
+  handleSave: (record: Item) => void;
+}
+
+export const EditableCell: React.FC<EditableCellProps> = ({
+  title,
+  editable,
+  children,
+  dataIndex,
+  record,
+  handleSave,
+  ...restProps
+}) => {
+  const [editing, setEditing] = useState(false);
+  const inputRef = useRef<InputRef>(null);
+  const form = useContext(EditableContext)!;
+
+  useEffect(() => {
+    if (editing) {
+      inputRef.current!.focus();
+    }
+  }, [editing]);
+
+  const toggleEdit = () => {
+    setEditing(!editing);
+    form.setFieldsValue({ [dataIndex]: record[dataIndex] });
+  };
+
+  const save = async () => {
+    try {
+      const values = await form.validateFields();
+
+      toggleEdit();
+      handleSave({ ...record, ...values });
+    } catch (errInfo) {
+      console.log('Save failed:', errInfo);
+    }
+  };
+
+  let childNode = children;
+
+  if (editable) {
+    childNode = editing ? (
+      <Form.Item
+        style={{ margin: 0 }}
+        name={dataIndex}
+        rules={[
+          {
+            required: true,
+            message: `${title} is required.`,
+          },
+        ]}
+      >
+        <Input ref={inputRef} onPressEnter={save} onBlur={save} />
+      </Form.Item>
+    ) : (
+      <div
+        className="editable-cell-value-wrap"
+        style={{ paddingRight: 24 }}
+        onClick={toggleEdit}
+      >
+        {children}
+      </div>
+    );
+  }
+
+  return <td {...restProps}>{childNode}</td>;
+};
diff --git a/web/src/pages/chat/chat-configuration-modal/index.less b/web/src/pages/chat/chat-configuration-modal/index.less
new file mode 100644
index 0000000..ab62428
--- /dev/null
+++ b/web/src/pages/chat/chat-configuration-modal/index.less
@@ -0,0 +1,43 @@
+.chatConfigurationDescription {
+  font-size: 14px;
+}
+
+.variableContainer {
+  padding-bottom: 20px;
+  .variableAlign {
+    text-align: right;
+  }
+
+  .variableLabel {
+    margin-right: 16px;
+  }
+
+  .variableTable {
+    margin-top: 14px;
+  }
+  .editableRow {
+    :global(.editable-cell) {
+      position: relative;
+    }
+
+    :global(.editable-cell-value-wrap) {
+      padding: 5px 12px;
+      cursor: pointer;
+      height: 22px !important;
+    }
+    &:hover {
+      :global(.editable-cell-value-wrap) {
+        padding: 4px 11px;
+        border: 1px solid #d9d9d9;
+        border-radius: 2px;
+      }
+    }
+  }
+}
+
+.segmentedHidden {
+  opacity: 0;
+  height: 0;
+  width: 0;
+  margin: 0;
+}
diff --git a/web/src/pages/chat/chat-configuration-modal/index.tsx b/web/src/pages/chat/chat-configuration-modal/index.tsx
new file mode 100644
index 0000000..9c253fe
--- /dev/null
+++ b/web/src/pages/chat/chat-configuration-modal/index.tsx
@@ -0,0 +1,107 @@
+import { ReactComponent as ChatConfigurationAtom } from '@/assets/svg/chat-configuration-atom.svg';
+import { IModalManagerChildrenProps } from '@/components/modal-manager';
+import { Divider, Flex, Form, Modal, Segmented } from 'antd';
+import { SegmentedValue } from 'antd/es/segmented';
+import { useState } from 'react';
+import AssistantSetting from './assistant-setting';
+import ModelSetting from './model-setting';
+import PromptEngine from './prompt-engine';
+
+import styles from './index.less';
+
+enum ConfigurationSegmented {
+  AssistantSetting = 'Assistant Setting',
+  ModelSetting = 'Model Setting',
+  PromptEngine = 'Prompt Engine',
+}
+
+const segmentedMap = {
+  [ConfigurationSegmented.AssistantSetting]: AssistantSetting,
+  [ConfigurationSegmented.ModelSetting]: ModelSetting,
+  [ConfigurationSegmented.PromptEngine]: PromptEngine,
+};
+
+const layout = {
+  labelCol: { span: 6 },
+  wrapperCol: { span: 18 },
+};
+
+const validateMessages = {
+  required: '${label} is required!',
+  types: {
+    email: '${label} is not a valid email!',
+    number: '${label} is not a valid number!',
+  },
+  number: {
+    range: '${label} must be between ${min} and ${max}',
+  },
+};
+
+const ChatConfigurationModal = ({
+  visible,
+  hideModal,
+}: IModalManagerChildrenProps) => {
+  const [form] = Form.useForm();
+  const [value, setValue] = useState<ConfigurationSegmented>(
+    ConfigurationSegmented.AssistantSetting,
+  );
+
+  const handleOk = async () => {
+    const x = await form.validateFields();
+    console.info(x);
+  };
+
+  const handleCancel = () => {
+    hideModal();
+  };
+
+  const handleSegmentedChange = (val: SegmentedValue) => {
+    setValue(val as ConfigurationSegmented);
+  };
+
+  const title = (
+    <Flex gap={16}>
+      <ChatConfigurationAtom></ChatConfigurationAtom>
+      <div>
+        <b>Chat Configuration</b>
+        <div className={styles.chatConfigurationDescription}>
+          Here, dress up a dedicated assistant for your special knowledge bases!
+          đź’•
+        </div>
+      </div>
+    </Flex>
+  );
+
+  return (
+    <Modal
+      title={title}
+      width={688}
+      open={visible}
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <Segmented
+        size={'large'}
+        value={value}
+        onChange={handleSegmentedChange}
+        options={Object.values(ConfigurationSegmented)}
+        block
+      />
+      <Divider></Divider>
+      <Form
+        {...layout}
+        name="nest-messages"
+        form={form}
+        style={{ maxWidth: 600 }}
+        validateMessages={validateMessages}
+        colon={false}
+      >
+        {Object.entries(segmentedMap).map(([key, Element]) => (
+          <Element key={key} show={key === value}></Element>
+        ))}
+      </Form>
+    </Modal>
+  );
+};
+
+export default ChatConfigurationModal;
diff --git a/web/src/pages/chat/chat-configuration-modal/interface.ts b/web/src/pages/chat/chat-configuration-modal/interface.ts
new file mode 100644
index 0000000..af3ab96
--- /dev/null
+++ b/web/src/pages/chat/chat-configuration-modal/interface.ts
@@ -0,0 +1,3 @@
+export interface ISegmentedContentProps {
+  show: boolean;
+}
diff --git a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx
new file mode 100644
index 0000000..5f76e4a
--- /dev/null
+++ b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx
@@ -0,0 +1,155 @@
+import { Divider, Flex, Form, InputNumber, Select, Slider } from 'antd';
+import classNames from 'classnames';
+import { ISegmentedContentProps } from './interface';
+
+import styles from './index.less';
+
+const { Option } = Select;
+
+const ModelSetting = ({ show }: ISegmentedContentProps) => {
+  return (
+    <section
+      className={classNames({
+        [styles.segmentedHidden]: !show,
+      })}
+    >
+      <Form.Item
+        label="Model"
+        name="model"
+        // rules={[{ required: true, message: 'Please input!' }]}
+      >
+        <Select />
+      </Form.Item>
+      <Divider></Divider>
+      <Form.Item
+        label="Parameters"
+        name="parameters"
+        // rules={[{ required: true, message: 'Please input!' }]}
+      >
+        <Select />
+      </Form.Item>
+      <Form.Item label="Temperature">
+        <Flex gap={20}>
+          <Flex flex={1}>
+            <Form.Item
+              name={['address', 'province']}
+              noStyle
+              rules={[{ required: true, message: 'Province is required' }]}
+            >
+              <Slider style={{ display: 'inline-block', width: '100%' }} />
+            </Form.Item>
+          </Flex>
+          <Form.Item
+            name={['address', 'street']}
+            noStyle
+            rules={[{ required: true, message: 'Street is required' }]}
+          >
+            <InputNumber
+              style={{
+                width: 50,
+              }}
+            />
+          </Form.Item>
+        </Flex>
+      </Form.Item>
+      <Form.Item label="Top P">
+        <Flex gap={20}>
+          <Flex flex={1}>
+            <Form.Item
+              name={['address', 'province']}
+              noStyle
+              rules={[{ required: true, message: 'Province is required' }]}
+            >
+              <Slider style={{ display: 'inline-block', width: '100%' }} />
+            </Form.Item>
+          </Flex>
+          <Form.Item
+            name={['address', 'street']}
+            noStyle
+            rules={[{ required: true, message: 'Street is required' }]}
+          >
+            <InputNumber
+              style={{
+                width: 50,
+              }}
+            />
+          </Form.Item>
+        </Flex>
+      </Form.Item>
+      <Form.Item label="Presence Penalty">
+        <Flex gap={20}>
+          <Flex flex={1}>
+            <Form.Item
+              name={['address', 'province']}
+              noStyle
+              rules={[{ required: true, message: 'Province is required' }]}
+            >
+              <Slider style={{ display: 'inline-block', width: '100%' }} />
+            </Form.Item>
+          </Flex>
+          <Form.Item
+            name={['address', 'street']}
+            noStyle
+            rules={[{ required: true, message: 'Street is required' }]}
+          >
+            <InputNumber
+              style={{
+                width: 50,
+              }}
+            />
+          </Form.Item>
+        </Flex>
+      </Form.Item>
+      <Form.Item label="Frequency Penalty">
+        <Flex gap={20}>
+          <Flex flex={1}>
+            <Form.Item
+              name={['address', 'province']}
+              noStyle
+              rules={[{ required: true, message: 'Province is required' }]}
+            >
+              <Slider style={{ display: 'inline-block', width: '100%' }} />
+            </Form.Item>
+          </Flex>
+          <Form.Item
+            name={['address', 'street']}
+            noStyle
+            rules={[{ required: true, message: 'Street is required' }]}
+          >
+            <InputNumber
+              style={{
+                width: 50,
+              }}
+            />
+          </Form.Item>
+        </Flex>
+      </Form.Item>
+      <Form.Item label="Max Tokens">
+        <Flex gap={20}>
+          <Flex flex={1}>
+            <Form.Item
+              name={['address', 'province']}
+              noStyle
+              rules={[{ required: true, message: 'Province is required' }]}
+            >
+              <Slider style={{ display: 'inline-block', width: '100%' }} />
+            </Form.Item>
+          </Flex>
+          <Form.Item
+            name={['address', 'street']}
+            noStyle
+            rules={[{ required: true, message: 'Street is required' }]}
+          >
+            <InputNumber
+              style={{
+                width: 50,
+              }}
+            />
+          </Form.Item>
+        </Flex>
+      </Form.Item>
+    </section>
+  );
+};
+
+export default ModelSetting;
diff --git a/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx
new file mode 100644
index 0000000..9df92e4
--- /dev/null
+++ b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx
@@ -0,0 +1,163 @@
+import { DeleteOutlined } from '@ant-design/icons';
+import {
+  Button,
+  Col,
+  Divider,
+  Form,
+  Input,
+  Row,
+  Select,
+  Switch,
+  Table,
+  TableProps,
+} from 'antd';
+import classNames from 'classnames';
+import { useState } from 'react';
+import { v4 as uuid } from 'uuid';
+import { EditableCell, EditableRow } from './editable-cell';
+import { ISegmentedContentProps } from './interface';
+
+import styles from './index.less';
+
+interface DataType {
+  key: string;
+  optional: boolean;
+}
+
+const { Option } = Select;
+
+const PromptEngine = ({ show }: ISegmentedContentProps) => {
+  const [dataSource, setDataSource] = useState<DataType[]>([]);
+
+  const components = {
+    body: {
+      row: EditableRow,
+      cell: EditableCell,
+    },
+  };
+
+  const handleRemove = (key: string) => () => {
+    const newData = dataSource.filter((item) => item.key !== key);
+    setDataSource(newData);
+  };
+
+  const handleSave = (row: DataType) => {
+    const newData = [...dataSource];
+    const index = newData.findIndex((item) => row.key === item.key);
+    const item = newData[index];
+    newData.splice(index, 1, {
+      ...item,
+      ...row,
+    });
+    setDataSource(newData);
+  };
+
+  const columns: TableProps<DataType>['columns'] = [
+    {
+      title: 'key',
+      dataIndex: 'variable',
+      key: 'variable',
+      onCell: (record: DataType) => ({
+        record,
+        editable: true,
+        dataIndex: 'variable',
+        title: 'key',
+        handleSave,
+      }),
+    },
+    {
+      title: 'optional',
+      dataIndex: 'optional',
+      key: 'optional',
+      width: 40,
+      align: 'center',
+      render() {
+        return <Switch size="small" />;
+      },
+    },
+    {
+      title: 'operation',
+      dataIndex: 'operation',
+      width: 30,
+      key: 'operation',
+      align: 'center',
+      render(_, record) {
+        return <DeleteOutlined onClick={handleRemove(record.key)} />;
+      },
+    },
+  ];
+
+  const handleAdd = () => {
+    setDataSource((state) => [
+      ...state,
+      {
+        key: uuid(),
+        variable: '',
+        optional: true,
+      },
+    ]);
+  };
+
+  return (
+    <section
+      className={classNames({
+        [styles.segmentedHidden]: !show,
+      })}
+    >
+      <Form.Item
+        label="Orchestrate"
+        name="orchestrate"
+        rules={[{ required: true, message: 'Please input!' }]}
+      >
+        <Input.TextArea autoSize={{ maxRows: 5, minRows: 5 }} />
+      </Form.Item>
+      <Divider></Divider>
+      <section className={classNames(styles.variableContainer)}>
+        <Row align={'middle'} justify="end">
+          <Col span={6} className={styles.variableAlign}>
+            <label className={styles.variableLabel}>Variables</label>
+          </Col>
+          <Col span={18} className={styles.variableAlign}>
+            <Button size="small" onClick={handleAdd}>
+              Add
+            </Button>
+          </Col>
+        </Row>
+        {dataSource.length > 0 && (
+          <Row>
+            <Col span={6}></Col>
+            <Col span={18}>
+              <Table
+                dataSource={dataSource}
+                columns={columns}
+                rowKey={'key'}
+                className={styles.variableTable}
+                components={components}
+                rowClassName={() => styles.editableRow}
+              />
+            </Col>
+          </Row>
+        )}
+      </section>
+      <Form.Item
+        label="Select one context"
+        name="context"
+        rules={[
+          {
+            required: true,
+            message: 'Please select your favourite colors!',
+            type: 'array',
+          },
+        ]}
+      >
+        <Select mode="multiple" placeholder="Please select favourite colors">
+          <Option value="red">Red</Option>
+          <Option value="green">Green</Option>
+          <Option value="blue">Blue</Option>
+        </Select>
+      </Form.Item>
+    </section>
+  );
+};
+
+export default PromptEngine;
diff --git a/web/src/pages/chat/chat-container/index.less b/web/src/pages/chat/chat-container/index.less
new file mode 100644
index 0000000..147213c
--- /dev/null
+++ b/web/src/pages/chat/chat-container/index.less
@@ -0,0 +1,3 @@
+.chatContainer {
+  padding: 0 24px 24px;
+}
diff --git a/web/src/pages/chat/chat-container/index.tsx b/web/src/pages/chat/chat-container/index.tsx
new file mode 100644
index 0000000..bd8d837
--- /dev/null
+++ b/web/src/pages/chat/chat-container/index.tsx
@@ -0,0 +1,36 @@
+import { Button, Flex, Input } from 'antd';
+import { ChangeEventHandler, useState } from 'react';
+
+import styles from './index.less';
+
+const ChatContainer = () => {
+  const [value, setValue] = useState('');
+
+  const handlePressEnter = () => {
+    console.info(value);
+  };
+
+  const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
+    setValue(e.target.value);
+  };
+
+  return (
+    <Flex flex={1} className={styles.chatContainer} vertical>
+      <Flex flex={1}>xx</Flex>
+      <Input
+        size="large"
+        placeholder="Message Resume Assistant..."
+        value={value}
+        suffix={
+          <Button type="primary" onClick={handlePressEnter}>
+            Send
+          </Button>
+        }
+        onPressEnter={handlePressEnter}
+        onChange={handleInputChange}
+      />
+    </Flex>
+  );
+};
+
+export default ChatContainer;
diff --git a/web/src/pages/chat/index.less b/web/src/pages/chat/index.less
new file mode 100644
index 0000000..e0cb855
--- /dev/null
+++ b/web/src/pages/chat/index.less
@@ -0,0 +1,25 @@
+.chatWrapper {
+  height: 100%;
+
+  .chatAppWrapper {
+    width: 288px;
+    padding: 26px;
+  }
+  .chatTitleWrapper {
+    width: 220px;
+    padding: 26px 0;
+  }
+
+  .chatTitle {
+    padding: 5px 15px;
+  }
+
+  .chatTitleContent {
+    padding: 5px 10px;
+  }
+
+  .divider {
+    margin: 0;
+    height: 100%;
+  }
+}
diff --git a/web/src/pages/chat/index.tsx b/web/src/pages/chat/index.tsx
index f791322..898f8c3 100644
--- a/web/src/pages/chat/index.tsx
+++ b/web/src/pages/chat/index.tsx
@@ -1,8 +1,64 @@
+import { FormOutlined } from '@ant-design/icons';
+import { Button, Card, Divider, Flex, Space, Tag } from 'antd';
 import { useSelector } from 'umi';
+import ChatContainer from './chat-container';
+
+import ModalManager from '@/components/modal-manager';
+import ChatConfigurationModal from './chat-configuration-modal';
+import styles from './index.less';
 
 const Chat = () => {
   const { name } = useSelector((state: any) => state.chatModel);
-  return <div>chat:{name} </div>;
+
+  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>
+
+          <Divider></Divider>
+          <Card>
+            <p>Card content</p>
+          </Card>
+        </Flex>
+      </Flex>
+      <Divider type={'vertical'} className={styles.divider}></Divider>
+      <Flex className={styles.chatTitleWrapper}>
+        <Flex flex={1} vertical>
+          <Flex
+            justify={'space-between'}
+            align="center"
+            className={styles.chatTitle}
+          >
+            <Space>
+              <b>Chat</b>
+              <Tag>25</Tag>
+            </Space>
+            <FormOutlined />
+          </Flex>
+          <Divider></Divider>
+          <section className={styles.chatTitleContent}>today</section>
+        </Flex>
+      </Flex>
+      <Divider type={'vertical'} className={styles.divider}></Divider>
+      <ChatContainer></ChatContainer>
+    </Flex>
+  );
 };
 
 export default Chat;
diff --git a/web/src/pages/chat/message-box.tsx b/web/src/pages/chat/message-box.tsx
new file mode 100644
index 0000000..d20722d
--- /dev/null
+++ b/web/src/pages/chat/message-box.tsx
@@ -0,0 +1,45 @@
+// RCE CSS
+import { MessageList } from 'react-chat-elements';
+import 'react-chat-elements/dist/main.css';
+
+const ChatBox = () => {
+  return (
+    <div style={{ width: 600 }}>
+      {/* <MessageBox
+        position={'left'}
+        type={'photo'}
+        text={'react.svg'}
+        data={{
+          uri: 'https://facebook.github.io/react/img/logo.svg',
+          status: {
+            click: false,
+            loading: 0,
+          },
+        }}
+      /> */}
+
+      <MessageList
+        // referance={messageListReferance}
+        className="message-list"
+        lockable={true}
+        toBottomHeight={'100%'}
+        dataSource={[
+          {
+            position: 'right',
+            type: 'text',
+            text: 'Lorem ipsum dolor sit amet',
+            date: new Date(),
+          },
+          {
+            position: 'left',
+            type: 'text',
+            text: 'Lorem ipsum dolor sit amet',
+            date: new Date(),
+          },
+        ]}
+      />
+    </div>
+  );
+};
+
+export default ChatBox;
diff --git a/web/src/pages/knowledge/knowledge-card/index.less b/web/src/pages/knowledge/knowledge-card/index.less
index cf2aa02..43157d8 100644
--- a/web/src/pages/knowledge/knowledge-card/index.less
+++ b/web/src/pages/knowledge/knowledge-card/index.less
@@ -26,7 +26,7 @@
   border: 1px solid rgba(234, 236, 240, 1);
   box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
   padding: 24px;
-  max-width: 300px;
+  width: 300px;
   cursor: pointer;
 
   .titleWrapper {
diff --git a/web/src/pages/knowledge/knowledge-card/index.tsx b/web/src/pages/knowledge/knowledge-card/index.tsx
index 199b763..8f7b050 100644
--- a/web/src/pages/knowledge/knowledge-card/index.tsx
+++ b/web/src/pages/knowledge/knowledge-card/index.tsx
@@ -63,7 +63,7 @@ const KnowledgeCard = ({ item }: IProps) => {
     <Card className={styles.card} onClick={handleCardClick}>
       <div className={styles.container}>
         <div className={styles.content}>
-          <Avatar size={34} icon={<UserOutlined />} />
+          <Avatar size={34} icon={<UserOutlined />} src={item.avatar} />
 
           <span className={styles.delete}>
             <Dropdown
@@ -78,14 +78,14 @@ const KnowledgeCard = ({ item }: IProps) => {
         </div>
         <div className={styles.titleWrapper}>
           <span className={styles.title}>{item.name}</span>
-          <p>A comprehensive knowledge base for crafting effective resumes.</p>
+          <p>{item.description}</p>
         </div>
         <div className={styles.footer}>
           <div className={styles.footerTop}>
             <div className={styles.bottomLeft}>
               <FileTextOutlined className={styles.leftIcon} />
               <span className={styles.rightText}>
-                <Space>{item.doc_num}文档</Space>
+                <Space>{item.doc_num}Docs</Space>
               </span>
             </div>
           </div>
-- 
GitLab