You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bigtop.apache.org by wu...@apache.org on 2024/12/04 03:46:27 UTC

(bigtop-manager) branch main updated: BIGTOP-4292: Add AI assistant page (#115)

This is an automated email from the ASF dual-hosted git repository.

wuzhiguo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/bigtop-manager.git


The following commit(s) were added to refs/heads/main by this push:
     new 7c1b8527 BIGTOP-4292: Add AI assistant page (#115)
7c1b8527 is described below

commit 7c1b85277ae5546efa86cc110f05dc1a410d7861
Author: Fdefined <55...@users.noreply.github.com>
AuthorDate: Wed Dec 4 11:46:23 2024 +0800

    BIGTOP-4292: Add AI assistant page (#115)
---
 bigtop-manager-ui/.prettierignore                  |   7 +-
 bigtop-manager-ui/.prettierrc.js                   |  25 +-
 bigtop-manager-ui/index.html                       |  26 +-
 bigtop-manager-ui/package.json                     |   2 +-
 bigtop-manager-ui/pnpm-lock.yaml                   | 389 ++++++++++++---------
 bigtop-manager-ui/src/api/ai-assistant/index.ts    |  93 +++++
 .../scrollbar.scss => api/ai-assistant/types.ts}   |  50 +--
 bigtop-manager-ui/src/api/chatbot/index.ts         | 108 ------
 bigtop-manager-ui/src/api/chatbot/types.ts         |  94 -----
 bigtop-manager-ui/src/api/component/index.ts       |   4 +-
 bigtop-manager-ui/src/api/config/index.ts          |   8 +-
 bigtop-manager-ui/src/api/job/index.ts             |   5 +-
 bigtop-manager-ui/src/api/llm-config/index.ts      |  35 +-
 bigtop-manager-ui/src/api/request.ts               |  33 +-
 bigtop-manager-ui/src/api/sse/index.ts             |  33 +-
 bigtop-manager-ui/src/api/sse/types.ts             |   2 +-
 bigtop-manager-ui/src/api/stack/index.ts           |  10 +-
 .../src/assets/images/ai_disabled.png              | Bin 0 -> 17649 bytes
 bigtop-manager-ui/src/assets/images/ai_helper.png  | Bin 0 -> 44227 bytes
 .../src/assets/images/svg/chat_avatar.svg          |  26 ++
 .../src/assets/images/svg/chatbot.svg              |  41 +++
 .../images/svg/exit_screen.svg}                    |  38 +-
 .../images/svg/full_screen.svg}                    |  38 +-
 .../src/assets/images/svg/history.svg              |  31 ++
 .../detail.vue => assets/images/svg/plus_gray.svg} |  18 +-
 .../detail.vue => assets/images/svg/send.svg}      |  18 +-
 .../src/components/ai-assistant/chat-history.vue   | 281 +++++++++++++++
 .../src/components/ai-assistant/chat-input.vue     |  73 ++++
 .../src/components/ai-assistant/chat-message.vue   |  71 ++++
 .../src/components/ai-assistant/empty-content.vue  | 103 ++++++
 .../src/components/ai-assistant/index.vue          | 225 ++++++++++++
 .../src/components/chatbot/chat-msg-item.vue       | 104 ------
 .../src/components/chatbot/chat-window.vue         | 262 --------------
 .../src/components/chatbot/chatbot.vue             | 225 ------------
 .../src/components/chatbot/model-selector.vue      |  64 ----
 .../src/components/chatbot/platform-auth-form.vue  | 156 ---------
 .../components/chatbot/platform-auth-selector.vue  | 126 -------
 .../src/components/chatbot/platform-selection.vue  |  99 ------
 .../src/components/chatbot/select-menu.vue         | 157 ---------
 .../src/components/chatbot/thread-selector.vue     | 157 ---------
 .../src/components/common/auto-form/index.vue      |  53 +--
 .../src/components/common/auto-form/template.vue   |   5 +-
 .../src/components/common/button-group/index.vue   |  57 +++
 .../common/button-group/types.ts}                  |  40 +--
 bigtop-manager-ui/src/components/common/index.ts   |   4 +-
 .../src/components/common/markdown-view/index.vue  |  20 +-
 .../src/components/user-avatar/index.vue           |   6 +-
 bigtop-manager-ui/src/composables/use-chat-bot.ts  | 241 -------------
 .../src/{components/common => directives}/index.ts |  39 ++-
 bigtop-manager-ui/src/enums/route-exceptions.ts    |   2 +-
 bigtop-manager-ui/src/enums/state.ts               |   7 +-
 bigtop-manager-ui/src/layouts/footer.vue           |   4 +-
 bigtop-manager-ui/src/layouts/header.vue           |  29 +-
 bigtop-manager-ui/src/layouts/index.vue            |   3 +-
 bigtop-manager-ui/src/layouts/sider.vue            |  15 +-
 .../index.ts => locales/en_US/ai-assistant.ts}     |  39 +--
 bigtop-manager-ui/src/locales/en_US/common.ts      |   7 +-
 bigtop-manager-ui/src/locales/en_US/index.ts       |   4 +-
 bigtop-manager-ui/src/locales/en_US/llm-config.ts  |   3 +-
 .../index.ts => locales/zh_CN/ai-assistant.ts}     |  39 +--
 bigtop-manager-ui/src/locales/zh_CN/common.ts      |   4 +-
 bigtop-manager-ui/src/locales/zh_CN/index.ts       |   4 +-
 bigtop-manager-ui/src/locales/zh_CN/llm-config.ts  |   3 +-
 bigtop-manager-ui/src/main.ts                      |   2 +
 .../cluster/create.vue                             |   0
 .../cluster/detail.vue                             |   0
 .../cluster/index.vue                              |   0
 .../components/index.vue                           |   0
 .../hosts/create.vue                               |   0
 .../hosts/index.vue                                |   0
 .../infrastructures/index.vue                      |   0
 bigtop-manager-ui/src/pages/login/index.vue        |  29 +-
 .../llm-config/components/add-llm-item.vue         |  53 +--
 .../llm-config/components/llm-item.vue             |  21 +-
 .../llm-config/index.vue                           |  12 +-
 bigtop-manager-ui/src/pages/user/profile/index.vue |   7 +-
 bigtop-manager-ui/src/plugins/index.ts             |   2 +
 bigtop-manager-ui/src/router/routes/index.ts       |   5 +-
 .../src/router/routes/modules/clusters.ts          |  21 +-
 .../src/router/routes/modules/system.ts            |   6 +-
 bigtop-manager-ui/src/store/ai-assistant/index.ts  | 244 +++++++++++++
 bigtop-manager-ui/src/store/llm-config/config.ts   |  30 +-
 bigtop-manager-ui/src/store/llm-config/index.ts    |  59 +++-
 bigtop-manager-ui/src/store/menu/helper.ts         |   4 +-
 bigtop-manager-ui/src/store/menu/index.ts          |  35 +-
 bigtop-manager-ui/src/styles/ant-design-vue.scss   |   2 +-
 bigtop-manager-ui/src/styles/index.scss            |   4 +-
 bigtop-manager-ui/src/styles/marked.scss           |   3 +-
 bigtop-manager-ui/src/styles/scrollbar.scss        |   2 +-
 bigtop-manager-ui/src/styles/variables.scss        |   6 +-
 bigtop-manager-ui/src/utils/array.ts               |   9 +-
 bigtop-manager-ui/src/utils/router-util.ts         |   4 +-
 bigtop-manager-ui/src/utils/tools.ts               |  15 +
 bigtop-manager-ui/tsconfig.json                    |  27 +-
 94 files changed, 1882 insertions(+), 2585 deletions(-)

diff --git a/bigtop-manager-ui/.prettierignore b/bigtop-manager-ui/.prettierignore
index f4211017..1852c01b 100644
--- a/bigtop-manager-ui/.prettierignore
+++ b/bigtop-manager-ui/.prettierignore
@@ -15,4 +15,9 @@
 
 node_modules
 dist
-public
\ No newline at end of file
+public
+
+package.json
+pnpm-lock.yaml
+
+**/*.md
\ No newline at end of file
diff --git a/bigtop-manager-ui/.prettierrc.js b/bigtop-manager-ui/.prettierrc.js
index 6943d21c..a2007ad6 100644
--- a/bigtop-manager-ui/.prettierrc.js
+++ b/bigtop-manager-ui/.prettierrc.js
@@ -18,15 +18,16 @@
  */
 
 module.exports = {
-    useTabs: false,
-    semi: false,
-    vueIndentScriptAndStyle: true,
-    singleQuote: true,
-    quoteProps: 'as-needed',
-    jsxBracketSameLine: false,
-    jsxSingleQuote: true,
-    arrowParens: 'always',
-    htmlWhitespaceSensitivity: 'strict',
-    endOfLine: 'lf',
-    trailingComma: 'none'
-}
\ No newline at end of file
+  useTabs: false,
+  printWidth: 120,
+  semi: false,
+  vueIndentScriptAndStyle: true,
+  singleQuote: true,
+  quoteProps: 'as-needed',
+  jsxBracketSameLine: false,
+  jsxSingleQuote: true,
+  arrowParens: 'always',
+  htmlWhitespaceSensitivity: 'strict',
+  endOfLine: 'lf',
+  trailingComma: 'none'
+}
diff --git a/bigtop-manager-ui/index.html b/bigtop-manager-ui/index.html
index e8559bfd..24db7b5d 100644
--- a/bigtop-manager-ui/index.html
+++ b/bigtop-manager-ui/index.html
@@ -19,18 +19,16 @@
 
 <!doctype html>
 <html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/logo.svg" />
+    <!-- <script src="/lodash.min.js"></script> -->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Bigtop Manager</title>
+  </head>
 
-<head>
-  <meta charset="UTF-8" />
-  <link rel="icon" type="image/svg+xml" href="/logo.svg" />
-  <!-- <script src="/lodash.min.js"></script> -->
-  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <title>Bigtop Manager</title>
-</head>
-
-<body>
-  <div id="app"></div>
-  <script type="module" src="/src/main.ts"></script>
-</body>
-
-</html>
\ No newline at end of file
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>
diff --git a/bigtop-manager-ui/package.json b/bigtop-manager-ui/package.json
index 2c7a53d9..e50625af 100644
--- a/bigtop-manager-ui/package.json
+++ b/bigtop-manager-ui/package.json
@@ -65,4 +65,4 @@
     "iOS 7",
     "last 3 iOS versions"
   ]
-}
\ No newline at end of file
+}
diff --git a/bigtop-manager-ui/pnpm-lock.yaml b/bigtop-manager-ui/pnpm-lock.yaml
index c038a751..8202c1a7 100644
--- a/bigtop-manager-ui/pnpm-lock.yaml
+++ b/bigtop-manager-ui/pnpm-lock.yaml
@@ -616,56 +616,56 @@ packages:
     engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
     dev: true
 
-  /@rollup/rollup-android-arm-eabi@4.27.3:
-    resolution: {integrity: sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==}
+  /@rollup/rollup-android-arm-eabi@4.28.0:
+    resolution: {integrity: sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ==}
     cpu: [arm]
     os: [android]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-android-arm64@4.27.3:
-    resolution: {integrity: sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==}
+  /@rollup/rollup-android-arm64@4.28.0:
+    resolution: {integrity: sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA==}
     cpu: [arm64]
     os: [android]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-darwin-arm64@4.27.3:
-    resolution: {integrity: sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==}
+  /@rollup/rollup-darwin-arm64@4.28.0:
+    resolution: {integrity: sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q==}
     cpu: [arm64]
     os: [darwin]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-darwin-x64@4.27.3:
-    resolution: {integrity: sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==}
+  /@rollup/rollup-darwin-x64@4.28.0:
+    resolution: {integrity: sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w==}
     cpu: [x64]
     os: [darwin]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-freebsd-arm64@4.27.3:
-    resolution: {integrity: sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==}
+  /@rollup/rollup-freebsd-arm64@4.28.0:
+    resolution: {integrity: sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ==}
     cpu: [arm64]
     os: [freebsd]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-freebsd-x64@4.27.3:
-    resolution: {integrity: sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==}
+  /@rollup/rollup-freebsd-x64@4.28.0:
+    resolution: {integrity: sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA==}
     cpu: [x64]
     os: [freebsd]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-arm-gnueabihf@4.27.3:
-    resolution: {integrity: sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==}
+  /@rollup/rollup-linux-arm-gnueabihf@4.28.0:
+    resolution: {integrity: sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w==}
     cpu: [arm]
     os: [linux]
     libc: [glibc]
@@ -673,8 +673,8 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-arm-musleabihf@4.27.3:
-    resolution: {integrity: sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==}
+  /@rollup/rollup-linux-arm-musleabihf@4.28.0:
+    resolution: {integrity: sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg==}
     cpu: [arm]
     os: [linux]
     libc: [musl]
@@ -682,8 +682,8 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-arm64-gnu@4.27.3:
-    resolution: {integrity: sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==}
+  /@rollup/rollup-linux-arm64-gnu@4.28.0:
+    resolution: {integrity: sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg==}
     cpu: [arm64]
     os: [linux]
     libc: [glibc]
@@ -691,8 +691,8 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-arm64-musl@4.27.3:
-    resolution: {integrity: sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==}
+  /@rollup/rollup-linux-arm64-musl@4.28.0:
+    resolution: {integrity: sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw==}
     cpu: [arm64]
     os: [linux]
     libc: [musl]
@@ -700,8 +700,8 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-powerpc64le-gnu@4.27.3:
-    resolution: {integrity: sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==}
+  /@rollup/rollup-linux-powerpc64le-gnu@4.28.0:
+    resolution: {integrity: sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ==}
     cpu: [ppc64]
     os: [linux]
     libc: [glibc]
@@ -709,8 +709,8 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-riscv64-gnu@4.27.3:
-    resolution: {integrity: sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==}
+  /@rollup/rollup-linux-riscv64-gnu@4.28.0:
+    resolution: {integrity: sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g==}
     cpu: [riscv64]
     os: [linux]
     libc: [glibc]
@@ -718,8 +718,8 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-s390x-gnu@4.27.3:
-    resolution: {integrity: sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==}
+  /@rollup/rollup-linux-s390x-gnu@4.28.0:
+    resolution: {integrity: sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw==}
     cpu: [s390x]
     os: [linux]
     libc: [glibc]
@@ -727,8 +727,8 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-x64-gnu@4.27.3:
-    resolution: {integrity: sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==}
+  /@rollup/rollup-linux-x64-gnu@4.28.0:
+    resolution: {integrity: sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==}
     cpu: [x64]
     os: [linux]
     libc: [glibc]
@@ -736,8 +736,8 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-linux-x64-musl@4.27.3:
-    resolution: {integrity: sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==}
+  /@rollup/rollup-linux-x64-musl@4.28.0:
+    resolution: {integrity: sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw==}
     cpu: [x64]
     os: [linux]
     libc: [musl]
@@ -745,24 +745,24 @@ packages:
     dev: true
     optional: true
 
-  /@rollup/rollup-win32-arm64-msvc@4.27.3:
-    resolution: {integrity: sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==}
+  /@rollup/rollup-win32-arm64-msvc@4.28.0:
+    resolution: {integrity: sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg==}
     cpu: [arm64]
     os: [win32]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-win32-ia32-msvc@4.27.3:
-    resolution: {integrity: sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==}
+  /@rollup/rollup-win32-ia32-msvc@4.28.0:
+    resolution: {integrity: sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A==}
     cpu: [ia32]
     os: [win32]
     requiresBuild: true
     dev: true
     optional: true
 
-  /@rollup/rollup-win32-x64-msvc@4.27.3:
-    resolution: {integrity: sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==}
+  /@rollup/rollup-win32-x64-msvc@4.28.0:
+    resolution: {integrity: sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ==}
     cpu: [x64]
     os: [win32]
     requiresBuild: true
@@ -845,8 +845,8 @@ packages:
     resolution: {integrity: sha512-v+JFDu96+UYJ3/UWzB0mEglIS//MZXgRaJ4ubUPwOM0gvLc/kcQ3TWNYwENEK7/EcXGQVrW8h/XqednSjBd/Og==}
     dev: true
 
-  /@types/node@20.17.6:
-    resolution: {integrity: sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==}
+  /@types/node@20.17.9:
+    resolution: {integrity: sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==}
     dependencies:
       undici-types: 6.19.8
     dev: true
@@ -865,6 +865,12 @@ packages:
       '@types/node': 20.5.3
     dev: true
 
+  /@types/trusted-types@2.0.7:
+    resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@types/web-bluetooth@0.0.18:
     resolution: {integrity: sha512-v/ZHEj9xh82usl8LMR3GarzFY1IrbXJw5L4QfQhokjRV91q+SelFqxQWSep1ucXEZ22+dSTwLFkXeur25sPIbw==}
     dev: false
@@ -892,7 +898,7 @@ packages:
       ignore: 5.3.2
       natural-compare: 1.4.0
       semver: 7.6.3
-      ts-api-utils: 1.4.0(typescript@5.0.2)
+      ts-api-utils: 1.4.3(typescript@5.0.2)
       typescript: 5.0.2
     transitivePeerDependencies:
       - supports-color
@@ -941,7 +947,7 @@ packages:
       '@typescript-eslint/utils': 6.4.1(eslint@8.47.0)(typescript@5.0.2)
       debug: 4.3.7
       eslint: 8.47.0
-      ts-api-utils: 1.4.0(typescript@5.0.2)
+      ts-api-utils: 1.4.3(typescript@5.0.2)
       typescript: 5.0.2
     transitivePeerDependencies:
       - supports-color
@@ -967,7 +973,7 @@ packages:
       globby: 11.1.0
       is-glob: 4.0.3
       semver: 7.6.3
-      ts-api-utils: 1.4.0(typescript@5.0.2)
+      ts-api-utils: 1.4.3(typescript@5.0.2)
       typescript: 5.0.2
     transitivePeerDependencies:
       - supports-color
@@ -1026,8 +1032,8 @@ packages:
       tinyrainbow: 1.2.0
     dev: true
 
-  /@vitest/pretty-format@2.1.5:
-    resolution: {integrity: sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==}
+  /@vitest/pretty-format@2.1.8:
+    resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==}
     dependencies:
       tinyrainbow: 1.2.0
     dev: true
@@ -1043,7 +1049,7 @@ packages:
     resolution: {integrity: sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==}
     dependencies:
       '@vitest/pretty-format': 2.0.5
-      magic-string: 0.30.13
+      magic-string: 0.30.14
       pathe: 1.1.2
     dev: true
 
@@ -1121,7 +1127,7 @@ packages:
       '@vue/compiler-ssr': 3.4.37
       '@vue/shared': 3.4.37
       estree-walker: 2.0.2
-      magic-string: 0.30.13
+      magic-string: 0.30.14
       postcss: 8.4.41
       source-map-js: 1.2.1
 
@@ -1555,7 +1561,7 @@ packages:
       postcss: ^8.1.0
     dependencies:
       browserslist: 4.24.2
-      caniuse-lite: 1.0.30001680
+      caniuse-lite: 1.0.30001686
       fraction.js: 4.3.7
       normalize-range: 0.1.2
       picocolors: 1.1.1
@@ -1657,8 +1663,8 @@ packages:
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
     hasBin: true
     dependencies:
-      caniuse-lite: 1.0.30001680
-      electron-to-chromium: 1.5.63
+      caniuse-lite: 1.0.30001686
+      electron-to-chromium: 1.5.68
       node-releases: 2.0.18
       update-browserslist-db: 1.1.1(browserslist@4.24.2)
     dev: true
@@ -1707,13 +1713,13 @@ packages:
     resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
     dependencies:
       browserslist: 4.24.2
-      caniuse-lite: 1.0.30001680
+      caniuse-lite: 1.0.30001686
       lodash.memoize: 4.1.2
       lodash.uniq: 4.5.0
     dev: true
 
-  /caniuse-lite@1.0.30001680:
-    resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==}
+  /caniuse-lite@1.0.30001686:
+    resolution: {integrity: sha512-Y7deg0Aergpa24M3qLC5xjNklnKnhsmSyR/V89dLZ1n0ucJIFNs7PgR2Yfa/Zf6W79SbBicgtGxZr2juHkEUIA==}
     dev: true
 
   /chai@5.1.2:
@@ -2111,7 +2117,7 @@ packages:
       is-arguments: 1.1.1
       is-array-buffer: 3.0.4
       is-date-object: 1.0.5
-      is-regex: 1.1.4
+      is-regex: 1.2.0
       is-shared-array-buffer: 1.0.3
       isarray: 2.0.5
       object-is: 1.1.6
@@ -2119,9 +2125,9 @@ packages:
       object.assign: 4.1.5
       regexp.prototype.flags: 1.5.3
       side-channel: 1.0.6
-      which-boxed-primitive: 1.0.2
+      which-boxed-primitive: 1.1.0
       which-collection: 1.0.2
-      which-typed-array: 1.1.15
+      which-typed-array: 1.1.16
     dev: true
 
   /deep-is@0.1.4:
@@ -2134,7 +2140,7 @@ packages:
     dependencies:
       es-define-property: 1.0.0
       es-errors: 1.3.0
-      gopd: 1.0.1
+      gopd: 1.1.0
     dev: true
 
   /define-properties@1.2.1:
@@ -2254,8 +2260,10 @@ packages:
       domelementtype: 2.3.0
     dev: true
 
-  /dompurify@3.2.0:
-    resolution: {integrity: sha512-AMdOzK44oFWqHEi0wpOqix/fUNY707OmoeFDnbi3Q5I8uOpy21ufUA5cDJPr0bosxrflOVD/H2DMSvuGKJGfmQ==}
+  /dompurify@3.2.2:
+    resolution: {integrity: sha512-YMM+erhdZ2nkZ4fTNRTSI94mb7VG7uVF5vj5Zde7tImgnhZE3R6YW/IACGIHb2ux+QkEXMhe591N+5jWOmL4Zw==}
+    optionalDependencies:
+      '@types/trusted-types': 2.0.7
     dev: false
 
   /domutils@1.7.0:
@@ -2303,8 +2311,8 @@ packages:
       semver: 7.6.3
     dev: true
 
-  /electron-to-chromium@1.5.63:
-    resolution: {integrity: sha512-ddeXKuY9BHo/mw145axlyWjlJ1UBt4WK3AlvkT7W2AbqfRQoacVoRUCF6wL3uIx/8wT9oLKXzI+rFqHHscByaA==}
+  /electron-to-chromium@1.5.68:
+    resolution: {integrity: sha512-FgMdJlma0OzUYlbrtZ4AeXjKxKPk6KT8WOP8BjcqxWtlg8qyJQjRzPJzUtUn5GBg1oQ26hFs7HOOHJMYiJRnvQ==}
     dev: true
 
   /emoji-regex@8.0.0:
@@ -2360,24 +2368,24 @@ packages:
       es-errors: 1.3.0
       es-object-atoms: 1.0.0
       es-set-tostringtag: 2.0.3
-      es-to-primitive: 1.2.1
+      es-to-primitive: 1.3.0
       function.prototype.name: 1.1.6
       get-intrinsic: 1.2.4
       get-symbol-description: 1.0.2
       globalthis: 1.0.4
-      gopd: 1.0.1
+      gopd: 1.1.0
       has-property-descriptors: 1.0.2
-      has-proto: 1.0.3
-      has-symbols: 1.0.3
+      has-proto: 1.1.0
+      has-symbols: 1.1.0
       hasown: 2.0.2
       internal-slot: 1.0.7
       is-array-buffer: 3.0.4
       is-callable: 1.2.7
       is-data-view: 1.0.1
       is-negative-zero: 2.0.3
-      is-regex: 1.1.4
+      is-regex: 1.2.0
       is-shared-array-buffer: 1.0.3
-      is-string: 1.0.7
+      is-string: 1.1.0
       is-typed-array: 1.1.13
       is-weakref: 1.0.2
       object-inspect: 1.13.3
@@ -2391,10 +2399,10 @@ packages:
       string.prototype.trimstart: 1.0.8
       typed-array-buffer: 1.0.2
       typed-array-byte-length: 1.0.1
-      typed-array-byte-offset: 1.0.2
-      typed-array-length: 1.0.6
+      typed-array-byte-offset: 1.0.3
+      typed-array-length: 1.0.7
       unbox-primitive: 1.0.2
-      which-typed-array: 1.1.15
+      which-typed-array: 1.1.16
     dev: true
 
   /es-define-property@1.0.0:
@@ -2414,11 +2422,11 @@ packages:
     dependencies:
       call-bind: 1.0.7
       get-intrinsic: 1.2.4
-      has-symbols: 1.0.3
+      has-symbols: 1.1.0
       is-arguments: 1.1.1
       is-map: 2.0.3
       is-set: 2.0.3
-      is-string: 1.0.7
+      is-string: 1.1.0
       isarray: 2.0.5
       stop-iteration-iterator: 1.0.0
     dev: true
@@ -2443,13 +2451,13 @@ packages:
       hasown: 2.0.2
     dev: true
 
-  /es-to-primitive@1.2.1:
-    resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
+  /es-to-primitive@1.3.0:
+    resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
     engines: {node: '>= 0.4'}
     dependencies:
       is-callable: 1.2.7
       is-date-object: 1.0.5
-      is-symbol: 1.0.4
+      is-symbol: 1.1.0
     dev: true
 
   /esbuild@0.21.5:
@@ -2904,8 +2912,8 @@ packages:
     dependencies:
       es-errors: 1.3.0
       function-bind: 1.1.2
-      has-proto: 1.0.3
-      has-symbols: 1.0.3
+      has-proto: 1.1.0
+      has-symbols: 1.1.0
       hasown: 2.0.2
     dev: true
 
@@ -2987,7 +2995,7 @@ packages:
     engines: {node: '>= 0.4'}
     dependencies:
       define-properties: 1.2.1
-      gopd: 1.0.1
+      gopd: 1.1.0
     dev: true
 
   /globby@11.1.0:
@@ -3008,8 +3016,9 @@ packages:
       delegate: 3.2.0
     dev: false
 
-  /gopd@1.0.1:
-    resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
+  /gopd@1.1.0:
+    resolution: {integrity: sha512-FQoVQnqcdk4hVM4JN1eromaun4iuS34oStkdlLENLdpULsuQcTyXj8w7ayhuUfPwEYZ1ZOooOTT6fdA9Vmx/RA==}
+    engines: {node: '>= 0.4'}
     dependencies:
       get-intrinsic: 1.2.4
     dev: true
@@ -3058,13 +3067,15 @@ packages:
       es-define-property: 1.0.0
     dev: true
 
-  /has-proto@1.0.3:
-    resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==}
+  /has-proto@1.1.0:
+    resolution: {integrity: sha512-QLdzI9IIO1Jg7f9GT1gXpPpXArAn6cS31R1eEZqz08Gc+uQ8/XiqHWt17Fiw+2p6oTTIq5GXEpQkAlA88YRl/Q==}
     engines: {node: '>= 0.4'}
+    dependencies:
+      call-bind: 1.0.7
     dev: true
 
-  /has-symbols@1.0.3:
-    resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
+  /has-symbols@1.1.0:
+    resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
     engines: {node: '>= 0.4'}
     dev: true
 
@@ -3072,7 +3083,7 @@ packages:
     resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
     engines: {node: '>= 0.4'}
     dependencies:
-      has-symbols: 1.0.3
+      has-symbols: 1.1.0
     dev: true
 
   /has-value@0.3.1:
@@ -3215,8 +3226,16 @@ packages:
       get-intrinsic: 1.2.4
     dev: true
 
-  /is-bigint@1.0.4:
-    resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
+  /is-async-function@2.0.0:
+    resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      has-tostringtag: 1.0.2
+    dev: true
+
+  /is-bigint@1.1.0:
+    resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+    engines: {node: '>= 0.4'}
     dependencies:
       has-bigints: 1.0.2
     dev: true
@@ -3228,8 +3247,8 @@ packages:
       binary-extensions: 2.3.0
     dev: true
 
-  /is-boolean-object@1.1.2:
-    resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
+  /is-boolean-object@1.2.0:
+    resolution: {integrity: sha512-kR5g0+dXf/+kXnqI+lu0URKYPKgICtHGGNCDSB10AaUFj3o/HkB3u7WfpRBJGFopxxY0oH3ux7ZsDjLtK7xqvw==}
     engines: {node: '>= 0.4'}
     dependencies:
       call-bind: 1.0.7
@@ -3298,11 +3317,25 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /is-finalizationregistry@1.1.0:
+    resolution: {integrity: sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      call-bind: 1.0.7
+    dev: true
+
   /is-fullwidth-code-point@3.0.0:
     resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
     engines: {node: '>=8'}
     dev: true
 
+  /is-generator-function@1.0.10:
+    resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      has-tostringtag: 1.0.2
+    dev: true
+
   /is-glob@4.0.3:
     resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
     engines: {node: '>=0.10.0'}
@@ -3320,10 +3353,11 @@ packages:
     engines: {node: '>= 0.4'}
     dev: true
 
-  /is-number-object@1.0.7:
-    resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
+  /is-number-object@1.1.0:
+    resolution: {integrity: sha512-KVSZV0Dunv9DTPkhXwcZ3Q+tUc9TsaE1ZwX5J2WMvsSGS6Md8TFPun5uwh0yRdrNerI6vf/tbJxqSx4c1ZI1Lw==}
     engines: {node: '>= 0.4'}
     dependencies:
+      call-bind: 1.0.7
       has-tostringtag: 1.0.2
     dev: true
 
@@ -3361,12 +3395,14 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: false
 
-  /is-regex@1.1.4:
-    resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
+  /is-regex@1.2.0:
+    resolution: {integrity: sha512-B6ohK4ZmoftlUe+uvenXSbPJFo6U37BH7oO1B3nQH8f/7h27N56s85MhUtbFJAziz5dcmuR3i8ovUl35zp8pFA==}
     engines: {node: '>= 0.4'}
     dependencies:
       call-bind: 1.0.7
+      gopd: 1.1.0
       has-tostringtag: 1.0.2
+      hasown: 2.0.2
     dev: true
 
   /is-set@2.0.3:
@@ -3386,25 +3422,28 @@ packages:
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
     dev: true
 
-  /is-string@1.0.7:
-    resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
+  /is-string@1.1.0:
+    resolution: {integrity: sha512-PlfzajuF9vSo5wErv3MJAKD/nqf9ngAs1NFQYm16nUYFO2IzxJ2hcm+IOCg+EEopdykNNUhVq5cz35cAUxU8+g==}
     engines: {node: '>= 0.4'}
     dependencies:
+      call-bind: 1.0.7
       has-tostringtag: 1.0.2
     dev: true
 
-  /is-symbol@1.0.4:
-    resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
+  /is-symbol@1.1.0:
+    resolution: {integrity: sha512-qS8KkNNXUZ/I+nX6QT8ZS1/Yx0A444yhzdTKxCzKkNjQ9sHErBxJnJAgh+f5YhusYECEcjo4XcyH87hn6+ks0A==}
     engines: {node: '>= 0.4'}
     dependencies:
-      has-symbols: 1.0.3
+      call-bind: 1.0.7
+      has-symbols: 1.1.0
+      safe-regex-test: 1.0.3
     dev: true
 
   /is-typed-array@1.1.13:
     resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==}
     engines: {node: '>= 0.4'}
     dependencies:
-      which-typed-array: 1.1.15
+      which-typed-array: 1.1.16
     dev: true
 
   /is-weakmap@2.0.2:
@@ -3467,7 +3506,7 @@ packages:
     resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
     engines: {node: '>= 10.13.0'}
     dependencies:
-      '@types/node': 20.17.6
+      '@types/node': 20.17.9
       merge-stream: 2.0.0
       supports-color: 8.1.1
     dev: true
@@ -3637,8 +3676,8 @@ packages:
     hasBin: true
     dev: true
 
-  /magic-string@0.30.13:
-    resolution: {integrity: sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==}
+  /magic-string@0.30.14:
+    resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==}
     dependencies:
       '@jridgewell/sourcemap-codec': 1.5.0
 
@@ -3797,8 +3836,8 @@ packages:
     resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==}
     dev: true
 
-  /nanoid@3.3.7:
-    resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+  /nanoid@3.3.8:
+    resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
 
@@ -3913,7 +3952,7 @@ packages:
     dependencies:
       call-bind: 1.0.7
       define-properties: 1.2.1
-      has-symbols: 1.0.3
+      has-symbols: 1.1.0
       object-keys: 1.1.1
     dev: true
 
@@ -4387,7 +4426,7 @@ packages:
     resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==}
     engines: {node: ^10 || ^12 || >=14}
     dependencies:
-      nanoid: 3.3.7
+      nanoid: 3.3.8
       picocolors: 1.1.1
       source-map-js: 1.2.1
 
@@ -4504,6 +4543,19 @@ packages:
       picomatch: 2.3.1
     dev: true
 
+  /reflect.getprototypeof@1.0.7:
+    resolution: {integrity: sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      call-bind: 1.0.7
+      define-properties: 1.2.1
+      es-abstract: 1.23.5
+      es-errors: 1.3.0
+      get-intrinsic: 1.2.4
+      gopd: 1.1.0
+      which-builtin-type: 1.2.0
+    dev: true
+
   /regenerator-runtime@0.14.1:
     resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
 
@@ -4567,31 +4619,31 @@ packages:
       glob: 7.2.3
     dev: true
 
-  /rollup@4.27.3:
-    resolution: {integrity: sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==}
+  /rollup@4.28.0:
+    resolution: {integrity: sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
     dependencies:
       '@types/estree': 1.0.6
     optionalDependencies:
-      '@rollup/rollup-android-arm-eabi': 4.27.3
-      '@rollup/rollup-android-arm64': 4.27.3
-      '@rollup/rollup-darwin-arm64': 4.27.3
-      '@rollup/rollup-darwin-x64': 4.27.3
-      '@rollup/rollup-freebsd-arm64': 4.27.3
-      '@rollup/rollup-freebsd-x64': 4.27.3
-      '@rollup/rollup-linux-arm-gnueabihf': 4.27.3
-      '@rollup/rollup-linux-arm-musleabihf': 4.27.3
-      '@rollup/rollup-linux-arm64-gnu': 4.27.3
-      '@rollup/rollup-linux-arm64-musl': 4.27.3
-      '@rollup/rollup-linux-powerpc64le-gnu': 4.27.3
-      '@rollup/rollup-linux-riscv64-gnu': 4.27.3
-      '@rollup/rollup-linux-s390x-gnu': 4.27.3
-      '@rollup/rollup-linux-x64-gnu': 4.27.3
-      '@rollup/rollup-linux-x64-musl': 4.27.3
-      '@rollup/rollup-win32-arm64-msvc': 4.27.3
-      '@rollup/rollup-win32-ia32-msvc': 4.27.3
-      '@rollup/rollup-win32-x64-msvc': 4.27.3
+      '@rollup/rollup-android-arm-eabi': 4.28.0
+      '@rollup/rollup-android-arm64': 4.28.0
+      '@rollup/rollup-darwin-arm64': 4.28.0
+      '@rollup/rollup-darwin-x64': 4.28.0
+      '@rollup/rollup-freebsd-arm64': 4.28.0
+      '@rollup/rollup-freebsd-x64': 4.28.0
+      '@rollup/rollup-linux-arm-gnueabihf': 4.28.0
+      '@rollup/rollup-linux-arm-musleabihf': 4.28.0
+      '@rollup/rollup-linux-arm64-gnu': 4.28.0
+      '@rollup/rollup-linux-arm64-musl': 4.28.0
+      '@rollup/rollup-linux-powerpc64le-gnu': 4.28.0
+      '@rollup/rollup-linux-riscv64-gnu': 4.28.0
+      '@rollup/rollup-linux-s390x-gnu': 4.28.0
+      '@rollup/rollup-linux-x64-gnu': 4.28.0
+      '@rollup/rollup-linux-x64-musl': 4.28.0
+      '@rollup/rollup-win32-arm64-msvc': 4.28.0
+      '@rollup/rollup-win32-ia32-msvc': 4.28.0
+      '@rollup/rollup-win32-x64-msvc': 4.28.0
       fsevents: 2.3.3
     dev: true
 
@@ -4607,7 +4659,7 @@ packages:
     dependencies:
       call-bind: 1.0.7
       get-intrinsic: 1.2.4
-      has-symbols: 1.0.3
+      has-symbols: 1.1.0
       isarray: 2.0.5
     dev: true
 
@@ -4621,7 +4673,7 @@ packages:
     dependencies:
       call-bind: 1.0.7
       es-errors: 1.3.0
-      is-regex: 1.1.4
+      is-regex: 1.2.0
     dev: true
 
   /safe-regex@1.1.0:
@@ -4703,7 +4755,7 @@ packages:
       es-errors: 1.3.0
       function-bind: 1.1.2
       get-intrinsic: 1.2.4
-      gopd: 1.0.1
+      gopd: 1.1.0
       has-property-descriptors: 1.0.2
     dev: true
 
@@ -5162,13 +5214,13 @@ packages:
     resolution: {integrity: sha512-hN4uFRxbK+PX56DxYiGHsTn2dME3TVr9vbNqlQGcGcPhJAn+tdP126iA+TArMpI4YSgnTkMWyoLl5bf81Hi5TA==}
     engines: {node: '>= 0.4'}
     dependencies:
-      gopd: 1.0.1
+      gopd: 1.1.0
       typedarray.prototype.slice: 1.0.3
-      which-typed-array: 1.1.15
+      which-typed-array: 1.1.16
     dev: true
 
-  /ts-api-utils@1.4.0(typescript@5.0.2):
-    resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==}
+  /ts-api-utils@1.4.3(typescript@5.0.2):
+    resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
     engines: {node: '>=16'}
     peerDependencies:
       typescript: '>=4.2.0'
@@ -5211,33 +5263,34 @@ packages:
     dependencies:
       call-bind: 1.0.7
       for-each: 0.3.3
-      gopd: 1.0.1
-      has-proto: 1.0.3
+      gopd: 1.1.0
+      has-proto: 1.1.0
       is-typed-array: 1.1.13
     dev: true
 
-  /typed-array-byte-offset@1.0.2:
-    resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==}
+  /typed-array-byte-offset@1.0.3:
+    resolution: {integrity: sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==}
     engines: {node: '>= 0.4'}
     dependencies:
       available-typed-arrays: 1.0.7
       call-bind: 1.0.7
       for-each: 0.3.3
-      gopd: 1.0.1
-      has-proto: 1.0.3
+      gopd: 1.1.0
+      has-proto: 1.1.0
       is-typed-array: 1.1.13
+      reflect.getprototypeof: 1.0.7
     dev: true
 
-  /typed-array-length@1.0.6:
-    resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==}
+  /typed-array-length@1.0.7:
+    resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
     engines: {node: '>= 0.4'}
     dependencies:
       call-bind: 1.0.7
       for-each: 0.3.3
-      gopd: 1.0.1
-      has-proto: 1.0.3
+      gopd: 1.1.0
       is-typed-array: 1.1.13
       possible-typed-array-names: 1.0.0
+      reflect.getprototypeof: 1.0.7
     dev: true
 
   /typedarray.prototype.slice@1.0.3:
@@ -5249,7 +5302,7 @@ packages:
       es-abstract: 1.23.5
       es-errors: 1.3.0
       typed-array-buffer: 1.0.2
-      typed-array-byte-offset: 1.0.2
+      typed-array-byte-offset: 1.0.3
     dev: true
 
   /typescript@5.0.2:
@@ -5262,8 +5315,8 @@ packages:
     dependencies:
       call-bind: 1.0.7
       has-bigints: 1.0.2
-      has-symbols: 1.0.3
-      which-boxed-primitive: 1.0.2
+      has-symbols: 1.1.0
+      which-boxed-primitive: 1.1.0
     dev: true
 
   /undici-types@6.19.8:
@@ -5403,7 +5456,7 @@ packages:
       '@types/node': 20.5.3
       esbuild: 0.21.5
       postcss: 8.4.41
-      rollup: 4.27.3
+      rollup: 4.28.0
       sass: 1.66.1
     optionalDependencies:
       fsevents: 2.3.3
@@ -5437,7 +5490,7 @@ packages:
       '@ampproject/remapping': 2.3.0
       '@types/node': 20.5.3
       '@vitest/expect': 2.0.5
-      '@vitest/pretty-format': 2.1.5
+      '@vitest/pretty-format': 2.1.8
       '@vitest/runner': 2.0.5
       '@vitest/snapshot': 2.0.5
       '@vitest/spy': 2.0.5
@@ -5446,7 +5499,7 @@ packages:
       debug: 4.3.7
       execa: 8.0.1
       happy-dom: 15.0.0
-      magic-string: 0.30.13
+      magic-string: 0.30.14
       pathe: 1.1.2
       std-env: 3.8.0
       tinybench: 2.9.0
@@ -5490,7 +5543,7 @@ packages:
     peerDependencies:
       vue: ^3.0.0
     dependencies:
-      dompurify: 3.2.0
+      dompurify: 3.2.2
       vue: 3.4.37(typescript@5.0.2)
     dev: false
 
@@ -5646,14 +5699,34 @@ packages:
     engines: {node: '>=12'}
     dev: true
 
-  /which-boxed-primitive@1.0.2:
-    resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
+  /which-boxed-primitive@1.1.0:
+    resolution: {integrity: sha512-Ei7Miu/AXe2JJ4iNF5j/UphAgRoma4trE6PtisM09bPygb3egMH3YLW/befsWb1A1AxvNSFidOFTB18XtnIIng==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      is-bigint: 1.1.0
+      is-boolean-object: 1.2.0
+      is-number-object: 1.1.0
+      is-string: 1.1.0
+      is-symbol: 1.1.0
+    dev: true
+
+  /which-builtin-type@1.2.0:
+    resolution: {integrity: sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==}
+    engines: {node: '>= 0.4'}
     dependencies:
-      is-bigint: 1.0.4
-      is-boolean-object: 1.1.2
-      is-number-object: 1.0.7
-      is-string: 1.0.7
-      is-symbol: 1.0.4
+      call-bind: 1.0.7
+      function.prototype.name: 1.1.6
+      has-tostringtag: 1.0.2
+      is-async-function: 2.0.0
+      is-date-object: 1.0.5
+      is-finalizationregistry: 1.1.0
+      is-generator-function: 1.0.10
+      is-regex: 1.2.0
+      is-weakref: 1.0.2
+      isarray: 2.0.5
+      which-boxed-primitive: 1.1.0
+      which-collection: 1.0.2
+      which-typed-array: 1.1.16
     dev: true
 
   /which-collection@1.0.2:
@@ -5666,14 +5739,14 @@ packages:
       is-weakset: 2.0.3
     dev: true
 
-  /which-typed-array@1.1.15:
-    resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==}
+  /which-typed-array@1.1.16:
+    resolution: {integrity: sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==}
     engines: {node: '>= 0.4'}
     dependencies:
       available-typed-arrays: 1.0.7
       call-bind: 1.0.7
       for-each: 0.3.3
-      gopd: 1.0.1
+      gopd: 1.1.0
       has-tostringtag: 1.0.2
     dev: true
 
diff --git a/bigtop-manager-ui/src/api/ai-assistant/index.ts b/bigtop-manager-ui/src/api/ai-assistant/index.ts
new file mode 100644
index 00000000..683fcff6
--- /dev/null
+++ b/bigtop-manager-ui/src/api/ai-assistant/index.ts
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import request from '@/api/request.ts'
+import type { ChatMessageItem, ChatThread, CreateChatThread, ThreadId, UpdateChatThread } from './types'
+import axios, { AxiosProgressEvent, AxiosResponse, CancelTokenSource } from 'axios'
+
+export const getThreadsFromAuthPlatform = (): Promise<ChatThread[]> => {
+  return request({
+    method: 'get',
+    url: '/llm/chatbot/threads'
+  })
+}
+
+export const getThread = (threadId: ThreadId): Promise<ChatThread> => {
+  return request({
+    method: 'get',
+    url: `/llm/chatbot/threads/${threadId}`
+  })
+}
+
+export const getThreadRecords = (threadId: ThreadId): Promise<ChatMessageItem[]> => {
+  return request({
+    method: 'get',
+    url: `/llm/chatbot/threads/${threadId}/history`
+  })
+}
+
+export const updateThread = (threadId: ThreadId, data: UpdateChatThread): Promise<ChatThread> => {
+  return request({
+    method: 'put',
+    url: `/llm/chatbot/threads/${threadId}`,
+    data
+  })
+}
+
+export const deleteThread = (threadId: ThreadId): Promise<boolean> => {
+  return request({
+    method: 'delete',
+    url: `/llm/chatbot/threads/${threadId}`
+  })
+}
+
+export const createChatThread = (data: CreateChatThread): Promise<ChatThread> => {
+  return request({
+    method: 'post',
+    url: '/llm/chatbot/threads',
+    data
+  })
+}
+
+export interface ChatMessagesRes<T> {
+  promise: Promise<T>
+  cancel: () => void
+}
+
+export const talkWithChatbot = (
+  threadId: ThreadId,
+  data: { message: string },
+  func: Function
+): ChatMessagesRes<AxiosResponse<ChatMessageItem[], any>> => {
+  const source: CancelTokenSource = axios.CancelToken.source()
+
+  const promise = request({
+    method: 'post',
+    url: `/llm/chatbot/threads/${threadId}/talk`,
+    responseType: 'stream',
+    data: {
+      message: data.message
+    },
+    timeout: 0,
+    cancelToken: source.token,
+    onDownloadProgress: (progressEvent: AxiosProgressEvent) => func(progressEvent)
+  })
+
+  return { promise, cancel: source.cancel }
+}
diff --git a/bigtop-manager-ui/src/styles/scrollbar.scss b/bigtop-manager-ui/src/api/ai-assistant/types.ts
similarity index 55%
copy from bigtop-manager-ui/src/styles/scrollbar.scss
copy to bigtop-manager-ui/src/api/ai-assistant/types.ts
index 4e38af8f..602974dd 100644
--- a/bigtop-manager-ui/src/styles/scrollbar.scss
+++ b/bigtop-manager-ui/src/api/ai-assistant/types.ts
@@ -17,35 +17,39 @@
  * under the License.
  */
 
-::-webkit-scrollbar {
-  width: 6px;
-  height: 6px;
+export enum Sender {
+  USER,
+  AI,
+  SYSTEM
 }
 
-::scrollbar {
-  width: 6px;
-  height: 6px;
+export type CreateChatThread = {
+  id?: number | string | null
+  name?: string | null
 }
 
-::-webkit-scrollbar-track {
-  display: none;
-  -webkit-box-shadow: inset 0 0 2px #b18933;
-}
+export type SenderType = keyof typeof Sender
+export type UpdateChatThread = CreateChatThread
+export type ThreadId = number | string
 
-::scrollbar-track {
-  display: none;
-  box-shadow: inset 0 0 2px #98f165;
-  border-radius: 1px;
+export interface ChatThread {
+  threadId?: number | string
+  authId?: number | string
+  platformId?: number | string
+  platformName?: string
+  model?: string
+  name?: string
+  createTime?: string
+  updateTime?: string
 }
 
-::-webkit-scrollbar-thumb {
-  border-radius: 10px;
-  background: #c1c1c1;
-  -webkit-box-shadow: inset 0 0 1px rgba(0, 102, 102, 0.144);
+export interface ChatMessageItem {
+  sender: SenderType
+  message: string
+  createTime?: string
 }
 
-::scrollbar-thumb {
-  border-radius: 10px;
-  background: #c1c1c1;
-  box-shadow: inset 0 0 1px rgba(0, 102, 102, 0.144);
-}
\ No newline at end of file
+export interface ReceivedMessageItem {
+  content?: string
+  finishReason?: 'completed' | null
+}
diff --git a/bigtop-manager-ui/src/api/chatbot/index.ts b/bigtop-manager-ui/src/api/chatbot/index.ts
deleted file mode 100644
index 0b098576..00000000
--- a/bigtop-manager-ui/src/api/chatbot/index.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import request from '@/api/request.ts'
-import {
-  AuthorizedPlatform,
-  SupportedPlatform,
-  CredentialFormItem,
-  AuthCredentialTestParams,
-  AuthTestResult,
-  ChatThread,
-  ChatThreadCondition,
-  ChatThreadHistoryCondition,
-  ChatThreadHistoryItem,
-  ChatThreadDelCondition
-} from '@/api/chatbot/types.ts'
-
-export const getAuthorizedPlatforms = (): Promise<AuthorizedPlatform[]> => {
-  return request({
-    method: 'get',
-    url: '/chatbot/auth-platforms'
-  })
-}
-export const getSupportedPlatforms = (): Promise<SupportedPlatform[]> => {
-  return request({
-    method: 'get',
-    url: '/chatbot/platforms'
-  })
-}
-export const getCredentialFormModelOfPlatform = (
-  platformId: string | number
-): Promise<CredentialFormItem[]> => {
-  return request({
-    method: 'get',
-    url: `/chatbot/platforms/${platformId}/auth-credentials`
-  })
-}
-
-export const validateAuthCredentials = (
-  data: AuthCredentialTestParams
-): Promise<AuthTestResult> => {
-  return request({
-    method: 'post',
-    url: '/chatbot/auth-platforms',
-    data
-  })
-}
-
-export const getChatThreads = (
-  params: ChatThreadCondition
-): Promise<ChatThread[]> => {
-  return request({
-    method: 'get',
-    url: `/chatbot/auth-platforms/${params.authId}/threads`,
-    params: {
-      model: params.model
-    }
-  })
-}
-export const createChatThread = (
-  data: ChatThreadCondition
-): Promise<ChatThread> => {
-  return request({
-    method: 'post',
-    url: `/chatbot/auth-platforms/${data.authId}/threads?model=${data.model}`
-  })
-}
-export const getThreadChatHistory = (
-  params: ChatThreadHistoryCondition
-): Promise<ChatThreadHistoryItem[]> => {
-  return request({
-    method: 'get',
-    url: `/chatbot/auth-platforms/${params.authId}/threads/${params.threadId}/history`
-  })
-}
-
-export const delAuthorizedPlatform = (
-  authId: string | number
-): Promise<boolean> => {
-  return request({
-    method: 'delete',
-    url: `/chatbot/auth-platforms/${authId}`
-  })
-}
-export const delChatThread = (
-  params: ChatThreadDelCondition
-): Promise<boolean> => {
-  return request({
-    method: 'delete',
-    url: `/chatbot/auth-platforms/${params.authId}/threads/${params.threadId}`
-  })
-}
diff --git a/bigtop-manager-ui/src/api/chatbot/types.ts b/bigtop-manager-ui/src/api/chatbot/types.ts
deleted file mode 100644
index 37bc7251..00000000
--- a/bigtop-manager-ui/src/api/chatbot/types.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export interface ChatbotConfig {
-  authId?: string | number
-  platformName?: string
-  supportModels?: string
-  model?: string
-  threadId?: string | number
-  threadName?: string
-  createTime?: string
-  updateTime?: string
-}
-export interface Platform {
-  id: string | number
-  platformId: string | number
-  platformName: string
-  supportModels: string
-  currModel?: string
-}
-
-export type AuthorizedPlatform = Platform
-
-export interface SupportedPlatform extends Platform {
-  id: string | number
-  name: string
-  supportModels: string
-}
-export interface CredentialFormItem {
-  name: string
-  displayName: string
-}
-export interface ChatThreadCondition {
-  authId: string | number
-  model: string
-}
-
-export interface ChatThread extends ChatThreadCondition {
-  threadId: string | number
-  threadName: string
-  createTime: string
-  updateTime: string
-}
-
-export interface AuthCredential {
-  key: string
-  value: string
-}
-
-export interface AuthCredentialTestParams {
-  platformId: string | number
-  authCredentials: AuthCredential[]
-}
-
-export interface AuthTestResult {
-  id: string | number
-  platformId: string | number
-  platformName: string
-  supportModels: string
-}
-
-export type Sender = 'USER' | 'SYSTEM' | 'AI'
-export interface ChatThreadHistoryItem {
-  sender: Sender
-  message: string
-  createTime?: string
-}
-
-export interface ChatThreadHistoryCondition {
-  authId: string | number
-  threadId: string | number
-}
-
-export interface SendChatMessageCondition extends ChatThreadHistoryCondition {
-  message: string
-}
-
-export type ChatThreadDelCondition = ChatThreadHistoryCondition
diff --git a/bigtop-manager-ui/src/api/component/index.ts b/bigtop-manager-ui/src/api/component/index.ts
index 9655ed4d..8a0d4fba 100644
--- a/bigtop-manager-ui/src/api/component/index.ts
+++ b/bigtop-manager-ui/src/api/component/index.ts
@@ -20,9 +20,7 @@
 import { HostComponentVO } from '@/api/component/types.ts'
 import request from '@/api/request.ts'
 
-export const getHostComponents = (
-  clusterId: number
-): Promise<HostComponentVO[]> => {
+export const getHostComponents = (clusterId: number): Promise<HostComponentVO[]> => {
   return request({
     method: 'get',
     url: '/clusters/' + clusterId + '/host-components'
diff --git a/bigtop-manager-ui/src/api/config/index.ts b/bigtop-manager-ui/src/api/config/index.ts
index ce50ca5c..5302a770 100644
--- a/bigtop-manager-ui/src/api/config/index.ts
+++ b/bigtop-manager-ui/src/api/config/index.ts
@@ -20,18 +20,14 @@
 import request from '@/api/request.ts'
 import { ServiceConfigVO } from '@/api/config/types.ts'
 
-export const getLatestConfigs = (
-  clusterId: number
-): Promise<ServiceConfigVO[]> => {
+export const getLatestConfigs = (clusterId: number): Promise<ServiceConfigVO[]> => {
   return request({
     method: 'get',
     url: '/clusters/' + clusterId + '/configurations/latest'
   })
 }
 
-export const getAllConfigs = (
-  clusterId: number
-): Promise<ServiceConfigVO[]> => {
+export const getAllConfigs = (clusterId: number): Promise<ServiceConfigVO[]> => {
   return request({
     method: 'get',
     url: '/clusters/' + clusterId + '/configurations'
diff --git a/bigtop-manager-ui/src/api/job/index.ts b/bigtop-manager-ui/src/api/job/index.ts
index d32ceb37..14f8e2aa 100644
--- a/bigtop-manager-ui/src/api/job/index.ts
+++ b/bigtop-manager-ui/src/api/job/index.ts
@@ -34,10 +34,7 @@ export const retryJob = (id: number, clusterId: number): Promise<JobVO> => {
   })
 }
 
-export const getJobs = (
-  clusterId: number,
-  pagination: Pagination
-): Promise<{ content: JobVO[]; total: number }> => {
+export const getJobs = (clusterId: number, pagination: Pagination): Promise<{ content: JobVO[]; total: number }> => {
   return request({
     method: 'get',
     url: `/clusters/${clusterId}/jobs`,
diff --git a/bigtop-manager-ui/src/api/llm-config/index.ts b/bigtop-manager-ui/src/api/llm-config/index.ts
index e3a8ce1f..4d0702bb 100644
--- a/bigtop-manager-ui/src/api/llm-config/index.ts
+++ b/bigtop-manager-ui/src/api/llm-config/index.ts
@@ -18,12 +18,7 @@
  */
 
 import request from '@/api/request.ts'
-import {
-  Platform,
-  PlatformCredential,
-  AuthorizedPlatform,
-  UpdateAuthorizedPlatformConfig
-} from './types'
+import { Platform, PlatformCredential, AuthorizedPlatform, UpdateAuthorizedPlatformConfig } from './types'
 
 export const getPlatforms = (): Promise<Platform[]> => {
   return request({
@@ -39,18 +34,14 @@ export const getAuthorizedPlatforms = (): Promise<AuthorizedPlatform[]> => {
   })
 }
 
-export const getPlatformCredentials = (
-  platformId: number
-): Promise<PlatformCredential[]> => {
+export const getPlatformCredentials = (platformId: number): Promise<PlatformCredential[]> => {
   return request({
     method: 'get',
     url: `/llm/config/platforms/${platformId}/auth-credentials`
   })
 }
 
-export const addAuthorizedPlatform = (
-  data: UpdateAuthorizedPlatformConfig
-): Promise<AuthorizedPlatform> => {
+export const addAuthorizedPlatform = (data: UpdateAuthorizedPlatformConfig): Promise<AuthorizedPlatform> => {
   return request({
     method: 'post',
     url: '/llm/config/auth-platforms',
@@ -58,18 +49,14 @@ export const addAuthorizedPlatform = (
   })
 }
 
-export const getAuthPlatformDetail = (
-  authId: number
-): Promise<UpdateAuthorizedPlatformConfig> => {
+export const getAuthPlatformDetail = (authId: number): Promise<UpdateAuthorizedPlatformConfig> => {
   return request({
     method: 'get',
     url: `/llm/config/auth-platforms/${authId}`
   })
 }
 
-export const updateAuthPlatform = (
-  data: UpdateAuthorizedPlatformConfig
-): Promise<UpdateAuthorizedPlatformConfig> => {
+export const updateAuthPlatform = (data: UpdateAuthorizedPlatformConfig): Promise<UpdateAuthorizedPlatformConfig> => {
   return request({
     method: 'put',
     url: `/llm/config/auth-platforms/${data.id}`,
@@ -84,27 +71,21 @@ export const deleteAuthPlatform = (authId: number): Promise<boolean> => {
   })
 }
 
-export const deactivateAuthorizedPlatform = (
-  authId: number
-): Promise<boolean> => {
+export const deactivateAuthorizedPlatform = (authId: number): Promise<boolean> => {
   return request({
     method: 'post',
     url: `/llm/config/auth-platforms/${authId}/deactivate`
   })
 }
 
-export const activateAuthorizedPlatform = (
-  authId: number
-): Promise<boolean> => {
+export const activateAuthorizedPlatform = (authId: number): Promise<boolean> => {
   return request({
     method: 'post',
     url: `/llm/config/auth-platforms/${authId}/activate`
   })
 }
 
-export const testAuthorizedPlatform = (
-  data: UpdateAuthorizedPlatformConfig
-): Promise<boolean> => {
+export const testAuthorizedPlatform = (data: UpdateAuthorizedPlatformConfig): Promise<boolean> => {
   return request({
     method: 'post',
     url: '/llm/config/auth-platforms/test',
diff --git a/bigtop-manager-ui/src/api/request.ts b/bigtop-manager-ui/src/api/request.ts
index 6b431304..b1c7771b 100644
--- a/bigtop-manager-ui/src/api/request.ts
+++ b/bigtop-manager-ui/src/api/request.ts
@@ -17,11 +17,7 @@
  * under the License.
  */
 
-import axios, {
-  AxiosError,
-  AxiosResponse,
-  InternalAxiosRequestConfig
-} from 'axios'
+import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
 import { message } from 'ant-design-vue'
 import { ResponseEntity } from '@/api/types'
 import router from '@/router'
@@ -35,26 +31,19 @@ const request = axios.create({
   timeout: API_EXPIRE_TIME
 })
 
-request.interceptors.request.use(
-  (
-    config: InternalAxiosRequestConfig<any>
-  ): InternalAxiosRequestConfig<any> => {
-    config.headers = config.headers || {}
+request.interceptors.request.use((config: InternalAxiosRequestConfig<any>): InternalAxiosRequestConfig<any> => {
+  config.headers = config.headers || {}
 
-    const locale = i18n.global.locale.value as Locale
-    config.headers['Accept-Language'] = locale.replace('_', '-')
+  const locale = i18n.global.locale.value as Locale
+  config.headers['Accept-Language'] = locale.replace('_', '-')
 
-    const token =
-      localStorage.getItem('Token') ??
-      sessionStorage.getItem('Token') ??
-      undefined
-    if (token) {
-      config.headers['Token'] = token
-    }
-
-    return config
+  const token = localStorage.getItem('Token') ?? sessionStorage.getItem('Token') ?? undefined
+  if (token) {
+    config.headers['Token'] = token
   }
-)
+
+  return config
+})
 
 request.interceptors.response.use(
   async (res: AxiosResponse) => {
diff --git a/bigtop-manager-ui/src/api/sse/index.ts b/bigtop-manager-ui/src/api/sse/index.ts
index abcd72d8..6b288b3b 100644
--- a/bigtop-manager-ui/src/api/sse/index.ts
+++ b/bigtop-manager-ui/src/api/sse/index.ts
@@ -19,14 +19,9 @@
 
 import axios, { type AxiosProgressEvent, type CancelTokenSource } from 'axios'
 import request from '@/api/request.ts'
-import type { chatMessagesRes, LogsRes } from './types'
-import type { SendChatMessageCondition } from '@/api/chatbot/types'
+import type { LogsRes } from './types'
 
-export const getLogs = (
-  clusterId: number,
-  id: number,
-  func: Function
-): LogsRes => {
+export const getLogs = (clusterId: number, id: number, func: Function): LogsRes => {
   const source: CancelTokenSource = axios.CancelToken.source()
 
   const promise = request({
@@ -35,29 +30,7 @@ export const getLogs = (
     responseType: 'stream',
     timeout: 0,
     cancelToken: source.token,
-    onDownloadProgress: (progressEvent: AxiosProgressEvent) =>
-      func(progressEvent)
-  })
-
-  return { promise, cancel: source.cancel }
-}
-export const sendChatMessage = (
-  data: SendChatMessageCondition,
-  func: Function
-): chatMessagesRes => {
-  const source: CancelTokenSource = axios.CancelToken.source()
-
-  const promise = request({
-    method: 'post',
-    url: `/chatbot/auth-platforms/${data.authId}/threads/${data.threadId}/talk`,
-    responseType: 'stream',
-    data: {
-      message: data.message
-    },
-    timeout: 0,
-    cancelToken: source.token,
-    onDownloadProgress: (progressEvent: AxiosProgressEvent) =>
-      func(progressEvent)
+    onDownloadProgress: (progressEvent: AxiosProgressEvent) => func(progressEvent)
   })
 
   return { promise, cancel: source.cancel }
diff --git a/bigtop-manager-ui/src/api/sse/types.ts b/bigtop-manager-ui/src/api/sse/types.ts
index 7bb9a342..8e169f04 100644
--- a/bigtop-manager-ui/src/api/sse/types.ts
+++ b/bigtop-manager-ui/src/api/sse/types.ts
@@ -21,7 +21,7 @@ export interface LogsRes {
   promise: Promise<any>
   cancel: () => void
 }
-export interface chatMessagesRes {
+export interface ChatMessagesRes {
   promise: Promise<any>
   cancel: () => void
 }
diff --git a/bigtop-manager-ui/src/api/stack/index.ts b/bigtop-manager-ui/src/api/stack/index.ts
index 534add90..f7d40042 100644
--- a/bigtop-manager-ui/src/api/stack/index.ts
+++ b/bigtop-manager-ui/src/api/stack/index.ts
@@ -29,20 +29,14 @@ export const getStacks = (): Promise<StackVO[]> => {
   })
 }
 
-export const getStackComponents = (
-  stackName: string,
-  stackVersion: string
-): Promise<ServiceComponentVO[]> => {
+export const getStackComponents = (stackName: string, stackVersion: string): Promise<ServiceComponentVO[]> => {
   return request({
     method: 'get',
     url: '/stacks/' + stackName + '/' + stackVersion + '/components'
   })
 }
 
-export const getStackConfigs = (
-  stackName: string,
-  stackVersion: string
-): Promise<ServiceConfigVO[]> => {
+export const getStackConfigs = (stackName: string, stackVersion: string): Promise<ServiceConfigVO[]> => {
   return request({
     method: 'get',
     url: '/stacks/' + stackName + '/' + stackVersion + '/configurations'
diff --git a/bigtop-manager-ui/src/assets/images/ai_disabled.png b/bigtop-manager-ui/src/assets/images/ai_disabled.png
new file mode 100644
index 00000000..ac50b603
Binary files /dev/null and b/bigtop-manager-ui/src/assets/images/ai_disabled.png differ
diff --git a/bigtop-manager-ui/src/assets/images/ai_helper.png b/bigtop-manager-ui/src/assets/images/ai_helper.png
new file mode 100644
index 00000000..95c52903
Binary files /dev/null and b/bigtop-manager-ui/src/assets/images/ai_helper.png differ
diff --git a/bigtop-manager-ui/src/assets/images/svg/chat_avatar.svg b/bigtop-manager-ui/src/assets/images/svg/chat_avatar.svg
new file mode 100644
index 00000000..ef83998d
--- /dev/null
+++ b/bigtop-manager-ui/src/assets/images/svg/chat_avatar.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+-->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24"
+  viewBox="0 0 24 24" fill="none">
+  <path
+    d="M2.50186 21.1155C2.50186 21.3279 2.67904 21.5 2.89761 21.5L21.1012 21.5C21.3197 21.5 21.4969 21.3279 21.4969 21.1157L21.4969 20.6565C21.506 20.5182 21.5245 19.8277 21.0694 19.0644C20.7824 18.5832 20.3659 18.1674 19.8314 17.8287C19.1848 17.4189 18.3623 17.122 17.3673 16.9432C17.36 16.9423 16.6223 16.8445 15.8666 16.6515C14.5505 16.3153 14.4354 16.0178 14.4347 16.0149C14.427 15.9855 14.4157 15.9573 14.4014 15.9307C14.3906 15.8752 14.364 15.6664 14.4149 15.1068C14.544 13.6855 15.3064 [...]
+    stroke="rgba(51, 51, 51, 1)" stroke-width="2" stroke-linejoin="round">
+  </path>
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/assets/images/svg/chatbot.svg b/bigtop-manager-ui/src/assets/images/svg/chatbot.svg
new file mode 100644
index 00000000..cdaefe52
--- /dev/null
+++ b/bigtop-manager-ui/src/assets/images/svg/chatbot.svg
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+-->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24"
+  viewBox="0 0 24 24" fill="none">
+  <path fill="rgba(51, 51, 51, 1)"
+    d="M5.5 8L18.5 8C19.6046 8 20.5 8.89543 20.5 10L20.5 20C20.5 21.1046 19.6046 22 18.5 22L5.5 22C4.39543 22 3.5 21.1046 3.5 20L3.5 10C3.5 8.89543 4.39543 8 5.5 8ZM5.5 10C5.5 10 5.5 10 5.5 10L5.5 20C5.5 20 5.5 20 5.5 20L18.5 20C18.5 20 18.5 20 18.5 20L18.5 10C18.5 10 18.5 10 18.5 10L5.5 10Z">
+  </path>
+  <circle cx="8.5" cy="13" r="1" fill="#333333">
+  </circle>
+  <circle cx="15.5" cy="13" r="1" fill="#333333">
+  </circle>
+  <path
+    d="M10 16C9.4477 16 9 16.4477 9 17C9 17.5523 9.4477 18 10 18L10 16ZM14 18C14.5523 18 15 17.5523 15 17C15 16.4477 14.5523 16 14 16L14 18ZM10 18L14 18L14 16L10 16L10 18Z"
+    fill="#333333">
+  </path>
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M12 5L12 9">
+  </path>
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M2 13L2 17">
+  </path>
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M22 13L22 17">
+  </path>
+  <circle cx="12" cy="4" r="1" stroke="rgba(51, 51, 51, 1)" stroke-width="2">
+  </circle>
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/layouts/footer.vue b/bigtop-manager-ui/src/assets/images/svg/exit_screen.svg
similarity index 50%
copy from bigtop-manager-ui/src/layouts/footer.vue
copy to bigtop-manager-ui/src/assets/images/svg/exit_screen.svg
index 8e36ae10..58f1168a 100644
--- a/bigtop-manager-ui/src/layouts/footer.vue
+++ b/bigtop-manager-ui/src/assets/images/svg/exit_screen.svg
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
   ~ Licensed to the Apache Software Foundation (ASF) under one
   ~ or more contributor license agreements.  See the NOTICE file
@@ -16,25 +17,18 @@
   ~ specific language governing permissions and limitations
   ~ under the License.
 -->
-
-<script setup lang="ts">
-  import dayjs from 'dayjs'
-</script>
-
-<template>
-  <a-layout-footer>
-    <div class="copyright">
-      {{ `Copyright ©2011-${dayjs().year()}` }}
-      <a href="https://www.apache.org" target="_blank">
-        The Apache Software Foundation
-      </a>
-      . All rights reserved.
-    </div>
-  </a-layout-footer>
-</template>
-
-<style scoped lang="scss">
-  .copyright {
-    text-align: center;
-  }
-</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"
+  viewBox="0 0 16 16" fill="none">
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333" stroke-linejoin="round" stroke-linecap="round"
+    d="M11 2L11 5L14 5">
+  </path>
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333" stroke-linejoin="round" stroke-linecap="round"
+    d="M5 2L5 5L2 5">
+  </path>
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333" stroke-linejoin="round" stroke-linecap="round"
+    d="M5 14L5 11L2 11">
+  </path>
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333" stroke-linejoin="round" stroke-linecap="round"
+    d="M11 14L11 11L13.9665 11">
+  </path>
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/layouts/footer.vue b/bigtop-manager-ui/src/assets/images/svg/full_screen.svg
similarity index 50%
copy from bigtop-manager-ui/src/layouts/footer.vue
copy to bigtop-manager-ui/src/assets/images/svg/full_screen.svg
index 8e36ae10..c5bb8c21 100644
--- a/bigtop-manager-ui/src/layouts/footer.vue
+++ b/bigtop-manager-ui/src/assets/images/svg/full_screen.svg
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
   ~ Licensed to the Apache Software Foundation (ASF) under one
   ~ or more contributor license agreements.  See the NOTICE file
@@ -16,25 +17,18 @@
   ~ specific language governing permissions and limitations
   ~ under the License.
 -->
-
-<script setup lang="ts">
-  import dayjs from 'dayjs'
-</script>
-
-<template>
-  <a-layout-footer>
-    <div class="copyright">
-      {{ `Copyright ©2011-${dayjs().year()}` }}
-      <a href="https://www.apache.org" target="_blank">
-        The Apache Software Foundation
-      </a>
-      . All rights reserved.
-    </div>
-  </a-layout-footer>
-</template>
-
-<style scoped lang="scss">
-  .copyright {
-    text-align: center;
-  }
-</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"
+  viewBox="0 0 16 16" fill="none">
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333" stroke-linejoin="round" stroke-linecap="round"
+    d="M11 2L14 2L14 5">
+  </path>
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333" stroke-linejoin="round" stroke-linecap="round"
+    d="M14 11L14 14L11 14">
+  </path>
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333" stroke-linejoin="round" stroke-linecap="round"
+    d="M5 14L2 14L2 11">
+  </path>
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333" stroke-linejoin="round" stroke-linecap="round"
+    d="M2 5L2 2L5 2">
+  </path>
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/assets/images/svg/history.svg b/bigtop-manager-ui/src/assets/images/svg/history.svg
new file mode 100644
index 00000000..ca067326
--- /dev/null
+++ b/bigtop-manager-ui/src/assets/images/svg/history.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+-->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"
+  viewBox="0 0 16 16" fill="none">
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333" stroke-linejoin="round" stroke-linecap="round"
+    d="M1.93945 2.24243L1.93945 4.66667L4.3637 4.66667">
+  </path>
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333" stroke-linejoin="round" stroke-linecap="round"
+    d="M1.33337 8.00004C1.33337 11.6819 4.31814 14.6667 8.00004 14.6667C11.6819 14.6667 14.6667 11.6819 14.6667 8.00004C14.6667 4.31814 11.6819 1.33337 8.00004 1.33337C5.53271 1.33337 3.37841 2.67374 2.22563 4.66607">
+  </path>
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333" stroke-linejoin="round" stroke-linecap="round"
+    d="M8.00162 4L8.00122 8.00293L10.8277 10.8294">
+  </path>
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/pages/cluster-mange/cluster/detail.vue b/bigtop-manager-ui/src/assets/images/svg/plus_gray.svg
similarity index 61%
copy from bigtop-manager-ui/src/pages/cluster-mange/cluster/detail.vue
copy to bigtop-manager-ui/src/assets/images/svg/plus_gray.svg
index d7612673..29d96ac8 100644
--- a/bigtop-manager-ui/src/pages/cluster-mange/cluster/detail.vue
+++ b/bigtop-manager-ui/src/assets/images/svg/plus_gray.svg
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
   ~ Licensed to the Apache Software Foundation (ASF) under one
   ~ or more contributor license agreements.  See the NOTICE file
@@ -16,11 +17,12 @@
   ~ specific language governing permissions and limitations
   ~ under the License.
 -->
-
-<script setup lang="ts"></script>
-
-<template>
-  <div>detail </div>
-</template>
-
-<style scoped></style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"
+  viewBox="0 0 16 16" fill="none">
+  <path stroke="rgba(51, 51, 51, 0.25)" stroke-width="1.3333333333333333" stroke-linejoin="round" stroke-linecap="round"
+    d="M8.02013 3.33337L8.00793 12.6667">
+  </path>
+  <path stroke="rgba(51, 51, 51, 0.25)" stroke-width="1.3333333333333333" stroke-linejoin="round" stroke-linecap="round"
+    d="M3.33337 8L12.6667 8">
+  </path>
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/pages/cluster-mange/cluster/detail.vue b/bigtop-manager-ui/src/assets/images/svg/send.svg
similarity index 63%
copy from bigtop-manager-ui/src/pages/cluster-mange/cluster/detail.vue
copy to bigtop-manager-ui/src/assets/images/svg/send.svg
index d7612673..4d09ab7d 100644
--- a/bigtop-manager-ui/src/pages/cluster-mange/cluster/detail.vue
+++ b/bigtop-manager-ui/src/assets/images/svg/send.svg
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
   ~ Licensed to the Apache Software Foundation (ASF) under one
   ~ or more contributor license agreements.  See the NOTICE file
@@ -16,11 +17,12 @@
   ~ specific language governing permissions and limitations
   ~ under the License.
 -->
-
-<script setup lang="ts"></script>
-
-<template>
-  <div>detail </div>
-</template>
-
-<style scoped></style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24"
+  viewBox="0 0 24 24" fill="none">
+  <path d="M21.5 2.5L14.85 21.5L11.05 12.95L2.5 9.15L21.5 2.5Z" stroke="rgba(51, 51, 51, 1)" stroke-width="2"
+    stroke-linejoin="round">
+  </path>
+  <path stroke="rgba(51, 51, 51, 1)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"
+    d="M21.5 2.5L11.05 12.95">
+  </path>
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/components/ai-assistant/chat-history.vue b/bigtop-manager-ui/src/components/ai-assistant/chat-history.vue
new file mode 100644
index 00000000..ae18febc
--- /dev/null
+++ b/bigtop-manager-ui/src/components/ai-assistant/chat-history.vue
@@ -0,0 +1,281 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<script setup lang="ts">
+  import { ref, shallowReactive, shallowRef, toRefs, watch } from 'vue'
+  import { useAiChatStore } from '@/store/ai-assistant'
+  import { storeToRefs } from 'pinia'
+  import { formatTime } from '@/utils/tools'
+  import { EllipsisOutlined } from '@ant-design/icons-vue'
+  import type { ChatThread, ThreadId } from '@/api/ai-assistant/types'
+  import { message, Modal } from 'ant-design-vue'
+  import { useI18n } from 'vue-i18n'
+
+  interface Props {
+    historyType: 'large' | 'small'
+    visible?: boolean
+  }
+
+  interface ThreadOperation {
+    key: 'delete' | 'rename'
+    danger?: boolean
+  }
+
+  type ThreadOperationHandler = Record<'delete' | 'rename', (thread: ChatThread, idx: number) => void>
+
+  const { t } = useI18n()
+  const aiChatStore = useAiChatStore()
+  const { threads, currThread, threadLimit } = storeToRefs(aiChatStore)
+  const props = defineProps<Props>()
+  const { visible, historyType } = toRefs(props)
+
+  const open = ref(false)
+  const isRename = ref(false)
+  const title = ref('aiAssistant.history')
+  const newName = ref('aiAssistant.new_chat')
+  const selectKey = ref<ThreadId[]>([])
+  const editThread = ref<ChatThread>({})
+  const threadOperations = shallowRef<ThreadOperation[]>([{ key: 'rename' }, { key: 'delete', danger: true }])
+  const threadOperationHandlers = shallowReactive<ThreadOperationHandler>({
+    delete: (thread, idx) => handleDeleteConfirm(thread, idx),
+    rename: (thread) => {
+      editThread.value = thread
+      isRename.value = true
+      newName.value = thread.name as string
+    }
+  })
+
+  watch(currThread, (val) => {
+    selectKey.value = [val?.threadId || '']
+  })
+
+  watch(
+    () => [open.value, visible.value],
+    async ([open, visible]) => {
+      if (open || visible) {
+        await aiChatStore.getThreadsFromAuthPlatform()
+        Object.keys(currThread.value || {}).length <= 0 && (currThread.value = threads.value[0])
+        selectKey.value = [currThread.value?.threadId || '']
+      }
+    },
+    {
+      immediate: true
+    }
+  )
+
+  const controlVisible = (visible: boolean) => {
+    open.value = visible
+  }
+
+  const handleSelect = ({ key }: { key: ThreadId }) => {
+    aiChatStore.getThread(key)
+  }
+
+  const handleRenameConfirm = async (thread: ChatThread) => {
+    if (newName.value == '') newName.value = t('aiAssistant.new_chat')
+    isRename.value = false
+    const checkRes = await aiChatStore.updateChatThread(editThread.value, newName.value)
+    if (checkRes) {
+      thread.name = newName.value
+      editThread.value = {}
+      newName.value = ''
+    }
+  }
+
+  const handleDeleteConfirm = (thread: ChatThread, idx: number) => {
+    Modal.confirm({
+      title: t('common.delete_msg'),
+      async onOk() {
+        const success = await aiChatStore.deleteChatThread(thread)
+        if (success) {
+          message.success(t('common.delete_success'))
+          await aiChatStore.getThreadsFromAuthPlatform()
+          const threadCounts = threads.value.length
+          if (threadCounts === 0) {
+            currThread.value = {}
+          } else {
+            const isEqual = thread.threadId === currThread.value.threadId
+            idx >= threadCounts && (currThread.value = threads.value[0])
+            if (isEqual) {
+              currThread.value = threads.value[idx]
+              aiChatStore.getThread(currThread.value.threadId as ThreadId)
+            }
+          }
+        }
+      }
+    })
+  }
+
+  const handleThreadActions = (thread: ChatThread, idx: number, { key }: { key: 'delete' | 'rename' }) => {
+    threadOperationHandlers[key](thread, idx)
+  }
+
+  defineExpose({
+    controlVisible
+  })
+</script>
+
+<template>
+  <div class="chat-history">
+    <a-drawer
+      v-if="historyType === 'small'"
+      v-model:open="open"
+      :title="$t(title)"
+      :closable="false"
+      :mask="false"
+      :get-container="false"
+      :style="{ position: 'absolute' }"
+      :body-style="{
+        padding: '16px'
+      }"
+      placement="top"
+    >
+      <template #extra>
+        <a-button type="text" shape="circle" @click="controlVisible(false)">
+          <template #icon>
+            <svg-icon name="close" />
+          </template>
+        </a-button>
+      </template>
+      <main>
+        <a-empty v-if="threads.length == 0" />
+        <a-menu v-else v-model:selected-keys="selectKey" @select="handleSelect">
+          <a-menu-item v-for="thread in threads" :key="thread.threadId" :title="thread.name">
+            <div class="chat-history-item">
+              <a-typography-link :ellipsis="true" :content="`${thread.name}`" />
+              <span>{{ formatTime(thread.createTime) }}</span>
+            </div>
+          </a-menu-item>
+        </a-menu>
+      </main>
+    </a-drawer>
+    <template v-else>
+      <div v-if="visible" class="chat-history-large">
+        <header>
+          <a-typography-title :level="5">{{ $t(title) }}</a-typography-title>
+        </header>
+        <main>
+          <a-empty v-if="threads.length == 0" />
+          <a-menu v-else v-model:selected-keys="selectKey" @select="handleSelect">
+            <a-menu-item v-for="(thread, idx) in threads" :key="thread.threadId" :title="thread.name">
+              <div class="chat-history-item">
+                <a-input
+                  v-if="isRename && editThread.threadId === thread.threadId"
+                  :key="thread.threadId"
+                  :ref="
+                    (el: HTMLElement) => {
+                      isRename && el.focus()
+                    }
+                  "
+                  v-model:value="newName"
+                  @blur="handleRenameConfirm(thread)"
+                />
+                <template v-else>
+                  <a-typography-link :ellipsis="true" :content="`${thread.name}`" />
+                  <a-dropdown :trigger="['click']">
+                    <a-button type="text" shape="circle" @click.stop>
+                      <template #icon>
+                        <EllipsisOutlined />
+                      </template>
+                    </a-button>
+                    <template #overlay>
+                      <a-menu @click="handleThreadActions(thread, idx, $event)">
+                        <a-menu-item v-for="action in threadOperations" :key="action.key" :danger="action.danger">
+                          <span>{{ $t(`common.${action.key}`) }}</span>
+                        </a-menu-item>
+                      </a-menu>
+                    </template>
+                  </a-dropdown>
+                </template>
+              </div>
+            </a-menu-item>
+          </a-menu>
+          <div class="chat-history-limit">
+            <a-typography-text size="small" type="secondary" :content="$t('aiAssistant.show_only10_records')" />
+          </div>
+        </main>
+        <footer>
+          <a-button type="primary" :disabled="threadLimit" @click="aiChatStore.createChatThread(false)">
+            {{ $t('aiAssistant.new_chat') }}
+          </a-button>
+        </footer>
+      </div>
+    </template>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+  .chat-history {
+    &-limit {
+      text-align: center;
+      span {
+        font-size: 12px;
+      }
+    }
+    &-item {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      gap: $space-sm;
+      height: 100%;
+      span:last-child {
+        color: $color-text;
+      }
+    }
+    :deep(.ant-menu) {
+      background: initial !important;
+      border: none;
+      .ant-menu-item {
+        border-radius: 0;
+      }
+    }
+    &-large {
+      :deep(.ant-menu-item-selected) {
+        background-color: $color-white;
+      }
+      position: fixed;
+      left: 0;
+      top: 64px;
+      height: 100%;
+      z-index: 10;
+      width: 300px;
+      background-color: #f7f9fc;
+
+      display: grid;
+      grid-template-rows: repeat(1, 56px 1fr 158px);
+
+      header {
+        padding: $space-md;
+      }
+
+      main {
+        padding-inline: $space-md;
+        overflow: auto;
+      }
+
+      footer {
+        padding-inline: $space-md;
+        text-align: center;
+        button {
+          height: 40px;
+          width: 100%;
+        }
+      }
+    }
+  }
+</style>
diff --git a/bigtop-manager-ui/src/components/ai-assistant/chat-input.vue b/bigtop-manager-ui/src/components/ai-assistant/chat-input.vue
new file mode 100644
index 00000000..1b0b085b
--- /dev/null
+++ b/bigtop-manager-ui/src/components/ai-assistant/chat-input.vue
@@ -0,0 +1,73 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<script setup lang="ts">
+  import { ref } from 'vue'
+  import { useAiChatStore } from '@/store/ai-assistant'
+  import { storeToRefs } from 'pinia'
+  import { message } from 'ant-design-vue'
+  import { useI18n } from 'vue-i18n'
+
+  const { t } = useI18n()
+  const aiChatStore = useAiChatStore()
+  const { isSending, threads, chatRecords } = storeToRefs(aiChatStore)
+  const chatMessage = ref('')
+
+  const sendMessage = async () => {
+    if (chatMessage.value === '') {
+      message.error(t('aiAssistant.message_cannot_be_empty'))
+      return
+    }
+    if (threads.value.length === 0) {
+      chatRecords.value = []
+    }
+    aiChatStore.setChatRecordForSender('USER', chatMessage.value)
+    aiChatStore.collectReceiveMessage(chatMessage.value)
+    chatMessage.value = ''
+  }
+</script>
+<template>
+  <div class="chat-input">
+    <a-textarea
+      v-model:value="chatMessage"
+      :bordered="false"
+      :auto-size="{ minRows: 1, maxRows: 6 }"
+      :placeholder="$t('aiAssistant.enter_question')"
+    />
+    <a-button :disabled="isSending" :type="isSending ? 'default' : 'text'" shape="circle" @click="sendMessage">
+      <template #icon>
+        <svg-icon name="send" />
+      </template>
+    </a-button>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+  .chat-input {
+    margin: 0 auto;
+    width: 100%;
+    max-width: 800px;
+    display: flex;
+    border: 1px solid $color-fill;
+    border-radius: $space-sm;
+    min-height: 40px;
+    align-items: center;
+    padding-inline-end: 10px;
+  }
+</style>
diff --git a/bigtop-manager-ui/src/components/ai-assistant/chat-message.vue b/bigtop-manager-ui/src/components/ai-assistant/chat-message.vue
new file mode 100644
index 00000000..4499b183
--- /dev/null
+++ b/bigtop-manager-ui/src/components/ai-assistant/chat-message.vue
@@ -0,0 +1,71 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<script setup lang="ts">
+  import type { ChatMessageItem } from '@/api/ai-assistant/types'
+  import { computed } from 'vue'
+
+  interface Props {
+    record: ChatMessageItem
+  }
+
+  const props = defineProps<Props>()
+  const isUser = computed(() => props.record.sender === 'USER')
+</script>
+
+<template>
+  <div class="chat-item">
+    <div class="chat-item-avatar">
+      <svg-icon :name="isUser ? 'chat_avatar' : 'chatbot'" />
+    </div>
+    <article class="chat-item-message">
+      <div class="msg-wrp">
+        <markdown-view :mark-raw="$props.record.message" />
+      </div>
+    </article>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+  .chat-item {
+    gap: $space-md;
+    display: flex;
+    margin: $space-md 0;
+    &-avatar {
+      width: 32px;
+      height: 32px;
+      flex-shrink: 0;
+      border: 1px solid #e5e5e5;
+      border-radius: 50%;
+
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+    &-message {
+      flex: 1;
+      border-radius: 4px;
+      background-color: #f7f9fc;
+      .msg-wrp {
+        height: auto;
+        width: 100%;
+        padding: $space-sm $space-md;
+      }
+    }
+  }
+</style>
diff --git a/bigtop-manager-ui/src/components/ai-assistant/empty-content.vue b/bigtop-manager-ui/src/components/ai-assistant/empty-content.vue
new file mode 100644
index 00000000..bb63bd66
--- /dev/null
+++ b/bigtop-manager-ui/src/components/ai-assistant/empty-content.vue
@@ -0,0 +1,103 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<script setup lang="ts">
+  import { shallowRef } from 'vue'
+  import { usePngImage } from '@/utils/tools'
+  import { useAiChatStore } from '@/store/ai-assistant'
+  import { storeToRefs } from 'pinia'
+
+  interface Emits {
+    (event: 'goSetUp'): void
+  }
+
+  const emits = defineEmits<Emits>()
+
+  const imageStyle = shallowRef({
+    height: '200px',
+    marginBottom: '16px'
+  })
+  const aiChatStore = useAiChatStore()
+  const { hasActivePlatform, threads, chatRecords } = storeToRefs(aiChatStore)
+  const emptyState = usePngImage('ai_helper')
+  const disabledState = usePngImage('ai_disabled')
+
+  const goSetUpLlmConfig = () => {
+    emits('goSetUp')
+  }
+
+  const quickAsk = (message: string) => {
+    if (threads.value.length === 0) {
+      chatRecords.value = []
+    }
+    aiChatStore.setChatRecordForSender('USER', message)
+    aiChatStore.collectReceiveMessage(message)
+  }
+</script>
+
+<template>
+  <div class="empty-content">
+    <a-empty :image="hasActivePlatform ? emptyState : disabledState" :image-style="imageStyle">
+      <template #description>
+        <template v-if="hasActivePlatform">
+          <div class="say-hello">
+            <a-typography-title :level="5">
+              <div> {{ $t('aiAssistant.greeting') }} </div>
+              <div> {{ $t('aiAssistant.help') }} </div>
+            </a-typography-title>
+          </div>
+          <div class="feature-desc">
+            <a-typography-link underline @click="quickAsk($t('aiAssistant.bigtop_manager'))">
+              {{ $t('aiAssistant.bigtop_manager') }}
+            </a-typography-link>
+            <a-typography-link underline @click="quickAsk($t('aiAssistant.can_do_for_you'))">
+              {{ $t('aiAssistant.can_do_for_you') }}
+            </a-typography-link>
+            <a-typography-link underline @click="quickAsk($t('aiAssistant.big_data_news'))">
+              {{ $t('aiAssistant.big_data_news') }}
+            </a-typography-link>
+          </div>
+        </template>
+        <template v-else>
+          <a-typography-text>
+            <div> {{ $t('aiAssistant.ai_disabled_msg') }} </div>
+          </a-typography-text>
+          <a-typography-link underline @click="goSetUpLlmConfig"> {{ $t('aiAssistant.go_set_up') }} </a-typography-link>
+        </template>
+      </template>
+    </a-empty>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+  .empty-content {
+    height: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+
+  .say-hello {
+    margin-bottom: 48px;
+  }
+
+  .feature-desc {
+    display: grid;
+    gap: $space-sm;
+  }
+</style>
diff --git a/bigtop-manager-ui/src/components/ai-assistant/index.vue b/bigtop-manager-ui/src/components/ai-assistant/index.vue
new file mode 100644
index 00000000..871d5c13
--- /dev/null
+++ b/bigtop-manager-ui/src/components/ai-assistant/index.vue
@@ -0,0 +1,225 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<script setup lang="ts">
+  import { computed, ref, watch } from 'vue'
+  import { DrawerProps } from 'ant-design-vue'
+  import { useAiChatStore } from '@/store/ai-assistant'
+  import { useLlmConfigStore } from '@/store/llm-config'
+  import { storeToRefs } from 'pinia'
+  import EmptyContent from './empty-content.vue'
+  import ChatInput from './chat-input.vue'
+  import ChatHistory from './chat-history.vue'
+  import ChatMessage from './chat-message.vue'
+  import type { ChatMessageItem } from '@/api/ai-assistant/types'
+  import type { GroupItem } from '@/components/common/button-group/types'
+  import { useRouter } from 'vue-router'
+
+  enum Action {
+    ADD,
+    RECORDS,
+    FULLSCREEN,
+    EXITSCREEN,
+    CLOSE
+  }
+
+  type ActionType = keyof typeof Action
+
+  const router = useRouter()
+  const aiChatStore = useAiChatStore()
+  const llmConfigStore = useLlmConfigStore()
+  const { currThread, chatRecords, threads, messageReceiver, hasActivePlatform, loadingChatRecords } =
+    storeToRefs(aiChatStore)
+  const { currAuthPlatform } = storeToRefs(llmConfigStore)
+
+  const open = ref(false)
+  const title = ref('aiAssistant.ai_assistant')
+  const fullScreen = ref(false)
+  const actionHandlers = ref<Map<ActionType, (...args: any[]) => void>>(new Map())
+  const smallChatHistoryRef = ref<InstanceType<typeof ChatHistory> | null>(null)
+
+  const width = computed(() => (fullScreen.value ? 'calc(100% - 300px)' : 450))
+  const noChat = computed(() => Object.keys(currThread.value || {}).length === 0 || chatRecords.value.length === 0)
+  const historyVisible = computed(() => fullScreen.value && open.value)
+  const historyType = computed(() => (historyVisible.value ? 'large' : 'small'))
+  const recordReceiver = computed((): ChatMessageItem => ({ sender: 'AI', message: messageReceiver.value }))
+  const checkActions = computed(() => {
+    if (!hasActivePlatform.value) {
+      return filterActions(['CLOSE'])
+    }
+    return fullScreen.value
+      ? filterActions(['EXITSCREEN', 'CLOSE'])
+      : filterActions(['ADD', 'RECORDS', 'FULLSCREEN', 'CLOSE'])
+  })
+  const addIcon = computed(() => (threads.value.length >= 10 ? 'plus_gray' : 'plus'))
+  const addState = computed(() => threads.value.length >= 10 || loadingChatRecords.value || !hasActivePlatform.value)
+  const actionGroup = computed((): GroupItem<ActionType>[] => [
+    {
+      tip: 'new_chat',
+      icon: addIcon.value,
+      action: 'ADD',
+      clickEvent: () => aiChatStore.createChatThread(),
+      disabled: addState.value
+    },
+    {
+      tip: 'history',
+      icon: 'history',
+      action: 'RECORDS',
+      clickEvent: openRecord
+    },
+    {
+      tip: 'full_screen',
+      icon: 'full_screen',
+      action: 'FULLSCREEN',
+      clickEvent: toggleFullScreen
+    },
+    {
+      tip: 'exit_screen',
+      icon: 'exit_screen',
+      action: 'EXITSCREEN',
+      clickEvent: toggleFullScreen
+    },
+    { icon: 'close', action: 'CLOSE', clickEvent: () => controlVisible(false) }
+  ])
+
+  const styleConfig = computed((): DrawerProps => {
+    return {
+      headerStyle: { height: '56px' },
+      bodyStyle: { padding: 0 },
+      contentWrapperStyle: {
+        boxShadow: 'none',
+        top: '64px',
+        transform: historyType.value === 'large' ? 'auto' : 'translateX(-180)'
+      },
+      footerStyle: { border: 'none' }
+    }
+  })
+
+  watch(open, (val) => {
+    val && initConfig()
+  })
+
+  const controlVisible = (visible: boolean = true) => {
+    open.value = visible
+    fullScreen.value = false
+  }
+
+  const openRecord = () => {
+    smallChatHistoryRef.value?.controlVisible(true)
+  }
+
+  const toggleFullScreen = () => {
+    fullScreen.value = !fullScreen.value
+  }
+
+  const initConfig = async () => {
+    actionGroup.value.forEach(({ action, clickEvent }) => {
+      action && actionHandlers.value.set(action, clickEvent || (() => {}))
+    })
+    !hasActivePlatform.value && (await llmConfigStore.getAuthorizedPlatforms(false))
+    aiChatStore.initCurrThread()
+  }
+
+  const filterActions = (showActions?: ActionType[]) => {
+    if (!showActions) return actionGroup.value
+    return actionGroup.value.filter((groupItem) => groupItem.action && showActions.includes(groupItem.action))
+  }
+
+  const onActions = (item: GroupItem<ActionType>) => {
+    const handler = item.action && actionHandlers.value.get(item.action)
+    handler ? handler() : console.warn(`Unknown action: ${item.action}`)
+  }
+
+  const goSetUpLlmConfig = () => {
+    controlVisible(false)
+    router.push('/system-manage/llm-config')
+  }
+
+  defineExpose({
+    controlVisible
+  })
+</script>
+
+<template>
+  <div class="ai-assistant">
+    <chat-history :visible="historyVisible" :history-type="historyType" />
+    <a-drawer
+      v-model:open="open"
+      :title="$t(title)"
+      :width="width"
+      :mask="false"
+      :closable="false"
+      placement="right"
+      v-bind="styleConfig"
+    >
+      <template #extra>
+        <button-group i18n="aiAssistant" :groups="checkActions" @on-click="onActions">
+          <template #icon="{ item }">
+            <svg-icon :name="item.icon" />
+          </template>
+        </button-group>
+      </template>
+      <chat-history ref="smallChatHistoryRef" :history-type="historyType" />
+      <div class="chat">
+        <a-spin :spinning="loadingChatRecords">
+          <empty-content v-if="noChat" @go-set-up="goSetUpLlmConfig" />
+          <template v-else>
+            <div v-if="open" v-auto-scroll class="chat-content">
+              <chat-message v-for="(record, idx) in chatRecords" :key="idx" :record="record" />
+              <chat-message v-if="messageReceiver" :record="recordReceiver" />
+            </div>
+          </template>
+        </a-spin>
+      </div>
+      <template v-if="hasActivePlatform" #footer>
+        <chat-input />
+        <a-typography-text type="secondary" class="ai-assistant-desc">
+          {{ currAuthPlatform?.platformName }} {{ currAuthPlatform?.model }}
+        </a-typography-text>
+      </template>
+    </a-drawer>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+  .ai-assistant-desc {
+    display: block;
+    text-align: center;
+    font-size: 12px;
+    margin: $space-sm 0;
+  }
+
+  .chat {
+    height: 100%;
+    padding-inline: $space-md;
+    overflow: auto;
+
+    :deep(.ant-spin-nested-loading) {
+      height: 100% !important;
+      .ant-spin-container {
+        height: 100% !important;
+      }
+    }
+
+    .chat-content {
+      width: 100%;
+      max-width: 800px;
+      margin: 0 auto;
+    }
+  }
+</style>
diff --git a/bigtop-manager-ui/src/components/chatbot/chat-msg-item.vue b/bigtop-manager-ui/src/components/chatbot/chat-msg-item.vue
deleted file mode 100644
index 2d1ecb5e..00000000
--- a/bigtop-manager-ui/src/components/chatbot/chat-msg-item.vue
+++ /dev/null
@@ -1,104 +0,0 @@
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one
-  ~ or more contributor license agreements.  See the NOTICE file
-  ~ distributed with this work for additional information
-  ~ regarding copyright ownership.  The ASF licenses this file
-  ~ to you under the Apache License, Version 2.0 (the
-  ~ "License"); you may not use this file except in compliance
-  ~ with the License.  You may obtain a copy of the License at
-  ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing,
-  ~ software distributed under the License is distributed on an
-  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  ~ KIND, either express or implied.  See the License for the
-  ~ specific language governing permissions and limitations
-  ~ under the License.
--->
-<script setup lang="ts">
-  import { computed, ref, watchEffect } from 'vue'
-  import { ChatThreadHistoryItem } from '@/api/chatbot/types'
-
-  interface Props {
-    chatItem: ChatThreadHistoryItem
-  }
-  const props = defineProps<Props>()
-  const emits = defineEmits(['updatedMsg'])
-  const message = ref('')
-  const isRight = computed(() => props.chatItem.sender === 'USER')
-
-  watchEffect(() => {
-    message.value = props.chatItem.message
-    emits('updatedMsg')
-  })
-</script>
-
-<template>
-  <div class="chat-item" :class="[isRight ? 'chat-r' : '']">
-    <div class="chat-item-avatar">
-      <section v-if="!isRight" class="chat-head">
-        <svg-icon name="robot" style="margin: 0" />
-      </section>
-      <section v-else class="chat-head">
-        <svg-icon name="user" style="margin: 0" />
-      </section>
-    </div>
-    <article class="chat-item-msg" :class="[isRight ? 'msg-r' : 'msg-l']">
-      <div class="msg-wrp">
-        <mark-view :mark-raw="message" />
-      </div>
-    </article>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-  .chat-head {
-    @include flexbox($justify: center, $align: center);
-    width: 32px;
-    height: 32px;
-    border-radius: 50%;
-    overflow: hidden;
-    background-color: #fff;
-    border: 1px solid #e8e8e8;
-  }
-
-  .chat-item {
-    flex: 1;
-    margin: 22px 0;
-    box-sizing: border-box;
-    display: flex;
-    &-avatar {
-      flex: 0 0 44px;
-      @include flexbox($justify: center);
-    }
-    &-msg {
-      display: flex;
-      width: calc(100% - 40px - 4px);
-      box-sizing: border-box;
-      .msg-wrp {
-        height: auto;
-        width: 100%;
-        padding: 8px;
-        border-radius: 8px;
-        align-items: flex-start;
-        border: 1px solid #e8e8e8;
-        background-color: #fff;
-      }
-    }
-  }
-
-  .msg-r {
-    width: auto;
-    justify-content: flex-end;
-    padding-left: 44px;
-  }
-
-  .msg-l {
-    padding-right: 44px;
-  }
-
-  .chat-r {
-    flex-direction: row-reverse;
-  }
-</style>
diff --git a/bigtop-manager-ui/src/components/chatbot/chat-window.vue b/bigtop-manager-ui/src/components/chatbot/chat-window.vue
deleted file mode 100644
index 7a632468..00000000
--- a/bigtop-manager-ui/src/components/chatbot/chat-window.vue
+++ /dev/null
@@ -1,262 +0,0 @@
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one
-  ~ or more contributor license agreements.  See the NOTICE file
-  ~ distributed with this work for additional information
-  ~ regarding copyright ownership.  The ASF licenses this file
-  ~ to you under the Apache License, Version 2.0 (the
-  ~ "License"); you may not use this file except in compliance
-  ~ with the License.  You may obtain a copy of the License at
-  ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing,
-  ~ software distributed under the License is distributed on an
-  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  ~ KIND, either express or implied.  See the License for the
-  ~ specific language governing permissions and limitations
-  ~ under the License.
--->
-<script setup lang="ts">
-  import { scrollToBottom } from '@/utils/tools'
-  import { message } from 'ant-design-vue/es/components'
-  import { ref, computed, toRefs, watchEffect, watch, onActivated } from 'vue'
-  import { useI18n } from 'vue-i18n'
-  import type { Option } from './select-menu.vue'
-  import {
-    ChatbotConfig,
-    ChatThreadHistoryItem,
-    SendChatMessageCondition,
-    Sender
-  } from '@/api/chatbot/types'
-  import ChatMsgItem from './chat-msg-item.vue'
-  import useChatBot from '@/composables/use-chat-bot'
-
-  interface PlatformChatProps {
-    visible: boolean
-    isExpand: boolean
-    chatPayload: ChatbotConfig
-    currPage?: Option
-  }
-
-  const {
-    loading,
-    receiving,
-    messageReceiver,
-    fetchSendChatMessage,
-    fetchThreadChatHistory
-  } = useChatBot()
-  const { t } = useI18n()
-  const inputText = ref('')
-  const isMessageReceived = ref(true)
-  const tempHistory = ref<ChatThreadHistoryItem[]>([])
-  const msgInputRef = ref<HTMLInputElement | null>(null)
-  const props = defineProps<PlatformChatProps>()
-  const { visible, currPage, isExpand, chatPayload } = toRefs(props)
-
-  const sendable = computed(
-    () => inputText.value != '' && isMessageReceived.value
-  )
-  const tempMsg = computed<ChatThreadHistoryItem>(() => ({
-    sender: 'AI',
-    message: messageReceiver.value || '...'
-  }))
-
-  watch(isExpand, () => handleScrollToBottom())
-
-  watchEffect(async () => {
-    if (currPage.value?.nextPage === 'chat-window' && visible.value) {
-      const { authId, threadId } = chatPayload.value
-      const data = await fetchThreadChatHistory(
-        authId as string | number,
-        threadId as string | number
-      )
-      tempHistory.value = data as ChatThreadHistoryItem[]
-      loading.value = false
-      handleScrollToBottom()
-    }
-  })
-
-  const onInput = (e: Event) => {
-    inputText.value = (e.target as Element)?.textContent || ''
-  }
-
-  const receiveMessage = async () => {
-    try {
-      receiving.value = true
-      const { threadId, authId } = chatPayload.value
-      const res = await fetchSendChatMessage({
-        authId,
-        threadId,
-        message: inputText.value
-      } as SendChatMessageCondition)
-      if (res) {
-        isMessageReceived.value = res.state
-        updateThreadChatHistory('AI', res.message as string)
-        inputText.value = ''
-        receiving.value = false
-      }
-    } catch (error) {
-      console.log('error', error)
-    } finally {
-      isMessageReceived.value = true
-      handleScrollToBottom()
-    }
-  }
-
-  const updateThreadChatHistory = (sender: Sender, message: string) => {
-    tempHistory.value.push({
-      sender,
-      message,
-      createTime: new Date().toISOString()
-    })
-  }
-
-  const clearUpInputContent = () => {
-    msgInputRef.value!.innerHTML = ''
-  }
-
-  const sendMessage = () => {
-    isMessageReceived.value = false
-    if (inputText.value === '') {
-      message.warning(t('ai.empty_message'))
-      return
-    }
-    updateThreadChatHistory('USER', inputText.value as string)
-    handleScrollToBottom()
-    clearUpInputContent()
-    receiveMessage()
-  }
-
-  const handleScrollToBottom = () => {
-    scrollToBottom(document.querySelector('.chat-container') as HTMLElement)
-  }
-
-  onActivated(() => {
-    tempHistory.value = []
-    inputText.value = ''
-  })
-</script>
-
-<template>
-  <div class="platform-chat">
-    <section class="chat-container">
-      <chat-msg-item
-        v-for="(chatItem, index) of tempHistory"
-        :key="index"
-        :chat-item="chatItem"
-      />
-      <chat-msg-item
-        v-if="receiving"
-        :chat-item="tempMsg"
-        @updated-msg="handleScrollToBottom"
-      />
-    </section>
-    <footer>
-      <div class="msg-wrp">
-        <div
-          ref="msgInputRef"
-          class="msg-input"
-          data-placeholder="message chat"
-          :contenteditable="true"
-          @input="onInput"
-        ></div>
-        <div class="msg-input-suffix">
-          <a-button
-            :disabled="!sendable"
-            type="primary"
-            class="msg-input-send"
-            @click="sendMessage"
-          >
-            <svg-icon
-              :name="sendable ? 'send' : 'send-disabled'"
-              style="margin: 0"
-            />
-          </a-button>
-        </div>
-      </div>
-      <section>
-        {{ `${chatPayload?.platformName} ${chatPayload?.model}` }}
-      </section>
-    </footer>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-  .platform-chat {
-    @include flexbox($direction: column, $justify: space-between);
-    position: relative;
-    height: 100%;
-    padding: 4px !important;
-
-    .chat-container {
-      flex: 1 1 0%;
-      overflow: auto;
-      scroll-behavior: smooth;
-      background-color: #f8f8f8;
-    }
-
-    .msg-wrp {
-      @include flexbox($align: center);
-      border: 1px solid #e5e7eb;
-      border-bottom-left-radius: 8px;
-      border-bottom-right-radius: 8px;
-      padding: 6px;
-      overflow: hidden;
-      .ant-input {
-        border-color: transparent;
-        &:focus {
-          border-color: transparent;
-          box-shadow: none;
-        }
-        &:hover {
-          border-color: transparent;
-        }
-      }
-
-      .msg-input {
-        width: 100%;
-        max-height: 100px;
-        padding: 2px 8px;
-        white-space: normal;
-        transition: all 0.2s;
-        overflow: auto;
-        font-weight: 500;
-        &::before {
-          content: attr(data-placeholder);
-          color: #a9a9a9;
-          display: block;
-          overflow: hidden;
-          white-space: nowrap;
-          text-overflow: ellipsis;
-        }
-        &:not(:empty)::before {
-          content: '';
-        }
-        &:focus-visible {
-          outline: 0;
-          border-color: none;
-        }
-        &-suffix {
-          align-self: end;
-          cursor: pointer;
-        }
-        &-send {
-          width: 50px;
-          height: 34px;
-          @include flexbox($justify: flex-end, $align: center);
-          border-radius: 12px;
-        }
-      }
-    }
-
-    footer {
-      width: 100%;
-      section {
-        text-align: center;
-        font-size: 12px;
-        padding: 2px;
-        color: #a9a9a9;
-      }
-    }
-  }
-</style>
diff --git a/bigtop-manager-ui/src/components/chatbot/chatbot.vue b/bigtop-manager-ui/src/components/chatbot/chatbot.vue
deleted file mode 100644
index 38d64603..00000000
--- a/bigtop-manager-ui/src/components/chatbot/chatbot.vue
+++ /dev/null
@@ -1,225 +0,0 @@
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one
-  ~ or more contributor license agreements.  See the NOTICE file
-  ~ distributed with this work for additional information
-  ~ regarding copyright ownership.  The ASF licenses this file
-  ~ to you under the Apache License, Version 2.0 (the
-  ~ "License"); you may not use this file except in compliance
-  ~ with the License.  You may obtain a copy of the License at
-  ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing,
-  ~ software distributed under the License is distributed on an
-  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  ~ KIND, either express or implied.  See the License for the
-  ~ specific language governing permissions and limitations
-  ~ under the License.
--->
-<script setup lang="ts">
-  import { ref, computed, watchEffect, watch } from 'vue'
-  import { useSessionStorage } from '@vueuse/core'
-  import PlatformAuthSelector from './platform-auth-selector.vue'
-  import PlatformSelection from './platform-selection.vue'
-  import PlatformAuthForm from './platform-auth-form.vue'
-  import ModelSelector from './model-selector.vue'
-  import ThreadSelector from './thread-selector.vue'
-  import chatWindow from './chat-window.vue'
-  import type { Option } from './select-menu.vue'
-  import type { ChatbotConfig } from '@/api/chatbot/types'
-
-  const pages: Record<string, any> = {
-    'platform-auth-selector': PlatformAuthSelector,
-    'platform-selection': PlatformSelection,
-    'platform-auth-form': PlatformAuthForm,
-    'model-selector': ModelSelector,
-    'thread-selector': ThreadSelector,
-    'chat-window': chatWindow
-  }
-  const style: Record<string, string> = {
-    width: '100%',
-    boxSizing: 'border-box',
-    padding: '24px'
-  }
-  const withoutBack = ['chat-window', 'platform-auth-selector']
-
-  const visible = ref(false)
-  const currPage = ref<Option>({ nextPage: 'platform-auth-selector' })
-  const afterPages = ref<Option[]>([currPage.value])
-  const chatPayload = useSessionStorage<ChatbotConfig>('chatbot-payload', {})
-  const isExpand = useSessionStorage('is-expand', false)
-
-  const getCompName = computed(() => pages[currPage.value.nextPage])
-  const showChatOps = computed(() => 'chat-window' === currPage.value?.nextPage)
-  const showBack = computed(
-    () =>
-      withoutBack.includes(currPage.value.nextPage) ||
-      afterPages.value.length == 1
-  )
-
-  const visibleWindow = (close = false) => {
-    close ? (visible.value = false) : (visible.value = !visible.value)
-  }
-
-  const resetPageStatus = () => {
-    currPage.value = { nextPage: 'platform-auth-selector' }
-    afterPages.value = []
-  }
-
-  const onBack = () => {
-    afterPages.value.pop()
-    currPage.value = afterPages.value[afterPages.value.length - 1]
-  }
-
-  const skipStep = (nextPage: string) => {
-    currPage.value = { nextPage }
-    afterPages.value = [currPage.value]
-  }
-
-  const onFullScreen = () => {
-    isExpand.value = !isExpand.value
-  }
-
-  watch(visible, (val) => {
-    val && chatPayload.value.threadId && skipStep('chat-window')
-  })
-
-  watchEffect(() => {
-    if (visible.value) {
-      const nextPage = afterPages.value.map((v) => v?.nextPage)
-      if (!nextPage?.includes(currPage.value.nextPage)) {
-        afterPages.value?.push(currPage.value)
-      }
-      return
-    }
-    resetPageStatus()
-  })
-</script>
-
-<template>
-  <a-float-button type="default" @click="() => visibleWindow()">
-    <template #icon>
-      <div class="chatbot-icon">
-        <svg-icon name="robot" style="margin: 0" />
-      </div>
-    </template>
-  </a-float-button>
-
-  <teleport to="body">
-    <div :class="[isExpand ? 'chatbot-expand' : 'chatbot']">
-      <a-card
-        v-show="visible"
-        class="base-model"
-        :class="[isExpand ? 'chat-model-expand' : 'chat-model']"
-      >
-        <template #title>
-          <header>
-            <div class="header-left">
-              <svg-icon v-if="!showBack" name="left" @click="onBack" />
-              <svg-icon
-                v-if="showChatOps && !isExpand"
-                name="home"
-                @click="resetPageStatus"
-              />
-            </div>
-            <div v-if="showChatOps" class="header-middle">
-              {{ chatPayload?.threadName }}
-            </div>
-            <div class="header-right">
-              <template v-if="showChatOps">
-                <svg-icon name="history" @click="skipStep('thread-selector')" />
-                <svg-icon name="full-screen" @click="onFullScreen" />
-              </template>
-              <svg-icon name="close" @click="visibleWindow(true)" />
-            </div>
-          </header>
-        </template>
-        <keep-alive>
-          <component
-            :is="getCompName"
-            v-bind="{ style }"
-            v-model:currPage="currPage"
-            v-model:chat-payload="chatPayload"
-            :visible="visible"
-            :is-expand="isExpand"
-          ></component>
-        </keep-alive>
-      </a-card>
-    </div>
-  </teleport>
-</template>
-
-<style lang="scss" scoped>
-  .chatbot {
-    position: fixed;
-    bottom: 10%;
-    right: 0;
-    &-icon {
-      @include flexbox($justify: center, $align: center);
-    }
-  }
-
-  .chatbot-expand {
-    position: fixed;
-  }
-
-  .chat-model-expand {
-    position: fixed;
-    width: 80%;
-    height: 70%;
-    min-width: 350px;
-    min-height: 500px;
-    left: 50%;
-    top: 50%;
-    transform: translate(-50%, -50%);
-  }
-
-  .chat-model {
-    width: 350px;
-    height: 500px;
-    position: absolute;
-    right: 20px;
-    bottom: 0;
-  }
-
-  .base-model {
-    border-radius: 14px;
-    @include flexbox($direction: column);
-    box-shadow: rgba(0, 0, 0, 0.2) 0px 20px 30px;
-
-    :deep(.ant-card-head) {
-      padding: 0 14px;
-      min-height: 40px;
-    }
-
-    :deep(.ant-card-body) {
-      height: 100%;
-      padding: 0;
-      @include flexbox($direction: column);
-      overflow: auto;
-    }
-
-    header {
-      box-sizing: border-box;
-      display: flex;
-
-      & > div {
-        flex: 1;
-      }
-    }
-
-    .header {
-      &-left {
-        @include flexbox($align: center);
-      }
-
-      &-middle {
-        @include flexbox($justify: center, $align: center);
-      }
-
-      &-right {
-        @include flexbox($justify: flex-end, $align: center);
-      }
-    }
-  }
-</style>
diff --git a/bigtop-manager-ui/src/components/chatbot/model-selector.vue b/bigtop-manager-ui/src/components/chatbot/model-selector.vue
deleted file mode 100644
index a15b8472..00000000
--- a/bigtop-manager-ui/src/components/chatbot/model-selector.vue
+++ /dev/null
@@ -1,64 +0,0 @@
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one
-  ~ or more contributor license agreements.  See the NOTICE file
-  ~ distributed with this work for additional information
-  ~ regarding copyright ownership.  The ASF licenses this file
-  ~ to you under the Apache License, Version 2.0 (the
-  ~ "License"); you may not use this file except in compliance
-  ~ with the License.  You may obtain a copy of the License at
-  ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing,
-  ~ software distributed under the License is distributed on an
-  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  ~ KIND, either express or implied.  See the License for the
-  ~ specific language governing permissions and limitations
-  ~ under the License.
--->
-<script setup lang="ts">
-  import SelectMenu from './select-menu.vue'
-  import { toRefs, computed, toRaw } from 'vue'
-  import { useI18n } from 'vue-i18n'
-  import type { SelectData, Option } from './select-menu.vue'
-  import type { ChatbotConfig } from '@/api/chatbot/types'
-
-  interface PreChatProps {
-    visible: boolean
-    chatPayload: ChatbotConfig
-    currPage?: Option
-  }
-
-  const { t } = useI18n()
-  const props = defineProps<PreChatProps>()
-  const { chatPayload } = toRefs(props)
-  const emits = defineEmits(['update:currPage', 'update:chatPayload'])
-
-  const platformModel = computed<SelectData[]>(() => [
-    {
-      title: t('ai.select_model'),
-      options: (chatPayload.value?.supportModels as string)
-        .split(',')
-        .map((v) => ({ name: v, nextPage: 'thread-selector' })) as Option[]
-    }
-  ])
-
-  const onSelect = async (option: Option) => {
-    if (option.nextPage === 'thread-selector') {
-      const transformedData = {
-        ...toRaw(chatPayload.value),
-        model: option.name
-      }
-      emits('update:chatPayload', transformedData)
-    }
-    emits('update:currPage', option)
-  }
-</script>
-
-<template>
-  <div class="model-selector">
-    <select-menu :select-data="platformModel" @select="onSelect" />
-  </div>
-</template>
-
-<style scoped></style>
diff --git a/bigtop-manager-ui/src/components/chatbot/platform-auth-form.vue b/bigtop-manager-ui/src/components/chatbot/platform-auth-form.vue
deleted file mode 100644
index e3c5c19f..00000000
--- a/bigtop-manager-ui/src/components/chatbot/platform-auth-form.vue
+++ /dev/null
@@ -1,156 +0,0 @@
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one
-  ~ or more contributor license agreements.  See the NOTICE file
-  ~ distributed with this work for additional information
-  ~ regarding copyright ownership.  The ASF licenses this file
-  ~ to you under the Apache License, Version 2.0 (the
-  ~ "License"); you may not use this file except in compliance
-  ~ with the License.  You may obtain a copy of the License at
-  ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing,
-  ~ software distributed under the License is distributed on an
-  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  ~ KIND, either express or implied.  See the License for the
-  ~ specific language governing permissions and limitations
-  ~ under the License.
--->
-<script setup lang="ts">
-  import SelectMenu from './select-menu.vue'
-  import useChatBot from '@/composables/use-chat-bot'
-  import { computed, ref, toRaw, toRefs, watchEffect } from 'vue'
-  import { useI18n } from 'vue-i18n'
-  import type { SelectData, Option } from './select-menu.vue'
-  import type { FormInstance } from 'ant-design-vue'
-  import type { ChatbotConfig, CredentialFormItem } from '@/api/chatbot/types'
-
-  interface PlatformAuthFormProps {
-    visible: boolean
-    chatPayload: ChatbotConfig
-    currPage?: Option
-  }
-  type FormState = { [key: string]: string }
-
-  const { t } = useI18n()
-  const {
-    loading,
-    checkLoading,
-    testAuthPlatform,
-    fetchCredentialFormModelOfPlatform
-  } = useChatBot()
-  const props = defineProps<PlatformAuthFormProps>()
-  const { currPage, visible, chatPayload } = toRefs(props)
-  const formRef = ref<FormInstance>()
-  const formState = ref<FormState>({})
-  const credentialFormModel = ref<CredentialFormItem[]>([])
-  const emits = defineEmits(['update:currPage', 'update:chatPayload'])
-
-  const platformAuthForm = computed<SelectData[]>(() => [
-    {
-      title: t('ai.authorizing_platform', [currPage.value?.name]),
-      options: []
-    }
-  ])
-
-  watchEffect(() => {
-    formState.value = credentialFormModel.value.reduce((acc, cur) => {
-      acc[cur.name] = ''
-      return acc
-    }, {} as FormState)
-  })
-
-  watchEffect(async () => {
-    if (currPage.value?.nextPage === 'platform-auth-form' && visible.value) {
-      const { authId } = chatPayload.value
-      const data = await fetchCredentialFormModelOfPlatform(
-        authId as string | number
-      )
-      credentialFormModel.value = data as CredentialFormItem[]
-      loading.value = false
-    }
-  })
-
-  const onSuccess = async () => {
-    const { authId } = chatPayload.value
-    const data = await testAuthPlatform(
-      authId as string | number,
-      toRaw(formState.value)
-    )
-    if (data) {
-      const { id: authId, platformName, supportModels } = data
-      emits('update:currPage', {
-        ...currPage.value,
-        nextPage: 'model-selector'
-      })
-      emits('update:chatPayload', {
-        ...toRaw(chatPayload.value),
-        ...{
-          authId,
-          platformName,
-          supportModels
-        }
-      })
-    }
-    checkLoading.value = false
-  }
-
-  const onCheck = async () => {
-    if (!formRef.value) {
-      return
-    }
-    formRef.value
-      .validate()
-      .then(onSuccess)
-      .catch((error) => {
-        console.log('error', error)
-      })
-  }
-</script>
-
-<template>
-  <div class="platform-auth-form">
-    <a-spin :spinning="loading">
-      <select-menu :select-data="platformAuthForm">
-        <template #select-custom-content>
-          <a-form ref="formRef" :model="formState" :colon="false">
-            <a-form-item
-              v-for="item in credentialFormModel"
-              :key="item.name"
-              :label="item.displayName"
-              :name="item.name"
-              :rules="[
-                {
-                  required: true,
-                  message: `Please input ${item.displayName}!`
-                }
-              ]"
-            >
-              <a-input
-                v-model:value="formState[`${item.name}`]"
-                :placeholder="`please input ${item.displayName}`"
-              />
-            </a-form-item>
-          </a-form>
-        </template>
-      </select-menu>
-    </a-spin>
-    <footer>
-      <a-button :loading="checkLoading" type="primary" @click="onCheck">{{
-        loading ? $t('common.loading_text_verifying') : $t('common.confirm')
-      }}</a-button>
-    </footer>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-  .platform-auth-form {
-    height: 100%;
-    @include flexbox($direction: column, $justify: space-between);
-  }
-  footer {
-    width: 100%;
-    @include flexbox($justify: flex-end, $align: center);
-    padding-bottom: 20px;
-  }
-</style>
diff --git a/bigtop-manager-ui/src/components/chatbot/platform-auth-selector.vue b/bigtop-manager-ui/src/components/chatbot/platform-auth-selector.vue
deleted file mode 100644
index b689fd0c..00000000
--- a/bigtop-manager-ui/src/components/chatbot/platform-auth-selector.vue
+++ /dev/null
@@ -1,126 +0,0 @@
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one
-  ~ or more contributor license agreements.  See the NOTICE file
-  ~ distributed with this work for additional information
-  ~ regarding copyright ownership.  The ASF licenses this file
-  ~ to you under the Apache License, Version 2.0 (the
-  ~ "License"); you may not use this file except in compliance
-  ~ with the License.  You may obtain a copy of the License at
-  ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing,
-  ~ software distributed under the License is distributed on an
-  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  ~ KIND, either express or implied.  See the License for the
-  ~ specific language governing permissions and limitations
-  ~ under the License.
--->
-<script setup lang="ts">
-  import SelectMenu from './select-menu.vue'
-  import useChatBot from '@/composables/use-chat-bot'
-  import { computed, h, onActivated, ref, toRaw, toRefs, watch } from 'vue'
-  import { useI18n } from 'vue-i18n'
-  import { Modal } from 'ant-design-vue/es/components'
-  import { ExclamationCircleFilled } from '@ant-design/icons-vue/lib/icons'
-  import type { SelectData, Option } from './select-menu.vue'
-  import type { AuthorizedPlatform, ChatbotConfig } from '@/api/chatbot/types'
-
-  interface PlatformAuthSelectorProps {
-    visible: boolean
-    chatPayload: ChatbotConfig
-    currPage?: Option
-  }
-  const { t } = useI18n()
-  const { loading, fetchAuthorizedPlatforms, fetchDelAuthorizedPlatform } =
-    useChatBot()
-  const props = defineProps<PlatformAuthSelectorProps>()
-  const { visible, currPage, chatPayload } = toRefs(props)
-  const authorizedPlatforms = ref<AuthorizedPlatform[]>([])
-  const emits = defineEmits(['update:currPage', 'update:chatPayload'])
-
-  const formattedOptions = computed<Option[]>(() =>
-    authorizedPlatforms.value.map((v: AuthorizedPlatform) => ({
-      ...v,
-      id: v.id,
-      name: v.platformName,
-      nextPage: 'model-selector'
-    }))
-  )
-
-  watch(visible, async (val) => {
-    if (val && currPage.value?.nextPage === 'platform-auth-selector') {
-      getAllAuthorizedPlatforms()
-    }
-  })
-
-  const getAllAuthorizedPlatforms = async () => {
-    const data = await fetchAuthorizedPlatforms()
-    authorizedPlatforms.value = data as AuthorizedPlatform[]
-    loading.value = false
-  }
-
-  const platformAuthSelectors = computed<SelectData[]>(() => [
-    {
-      title: t('ai.select_authorized_platform'),
-      emptyOptionsText: t('ai.no_authorized_platform'),
-      isDeletable: true,
-      options: formattedOptions.value
-    },
-    {
-      title: t('ai.or_you_can'),
-      isDeletable: false,
-      options: [
-        {
-          nextPage: 'platform-selection',
-          name: t('ai.authorize_new_platform')
-        }
-      ]
-    }
-  ])
-
-  const onSelect = (option: Option) => {
-    if (option.nextPage === 'model-selector') {
-      const transformedData = {
-        ...toRaw(chatPayload.value),
-        authId: option.id,
-        platformName: option.name,
-        supportModels: option.supportModels
-      }
-      emits('update:chatPayload', transformedData)
-    }
-    emits('update:currPage', option)
-  }
-
-  const onRemove = (option: Option) => {
-    Modal.confirm({
-      title: t('common.delete_confirm_title'),
-      icon: h(ExclamationCircleFilled),
-      content: t('common.delete_confirm_content', [option.name]),
-      async onOk() {
-        const { id: authId } = option
-        await fetchDelAuthorizedPlatform(authId as string | number)
-        getAllAuthorizedPlatforms()
-        loading.value = false
-      }
-    })
-  }
-
-  onActivated(() => {
-    getAllAuthorizedPlatforms()
-  })
-</script>
-
-<template>
-  <div>
-    <a-spin :spinning="loading">
-      <select-menu
-        :select-data="platformAuthSelectors"
-        @select="onSelect"
-        @remove="onRemove"
-      ></select-menu>
-    </a-spin>
-  </div>
-</template>
-
-<style lang="scss" scoped></style>
diff --git a/bigtop-manager-ui/src/components/chatbot/platform-selection.vue b/bigtop-manager-ui/src/components/chatbot/platform-selection.vue
deleted file mode 100644
index 58a72083..00000000
--- a/bigtop-manager-ui/src/components/chatbot/platform-selection.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one
-  ~ or more contributor license agreements.  See the NOTICE file
-  ~ distributed with this work for additional information
-  ~ regarding copyright ownership.  The ASF licenses this file
-  ~ to you under the Apache License, Version 2.0 (the
-  ~ "License"); you may not use this file except in compliance
-  ~ with the License.  You may obtain a copy of the License at
-  ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing,
-  ~ software distributed under the License is distributed on an
-  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  ~ KIND, either express or implied.  See the License for the
-  ~ specific language governing permissions and limitations
-  ~ under the License.
--->
-<script setup lang="ts">
-  import SelectMenu from './select-menu.vue'
-  import useChatBot from '@/composables/use-chat-bot'
-  import { computed, watch, ref, toRefs, toRaw } from 'vue'
-  import { useI18n } from 'vue-i18n'
-  import type { SelectData, Option } from './select-menu.vue'
-  import type { ChatbotConfig, SupportedPlatform } from '@/api/chatbot/types'
-
-  interface PlatformSelection {
-    visible: boolean
-    chatPayload: ChatbotConfig
-    currPage?: Option
-  }
-
-  const { t } = useI18n()
-  const { loading, fetchSupportedPlatforms } = useChatBot()
-  const props = defineProps<PlatformSelection>()
-  const { visible, currPage, chatPayload } = toRefs(props)
-  const supportedPlatforms = ref<SupportedPlatform[]>([])
-  const emits = defineEmits(['update:currPage', 'update:chatPayload'])
-
-  const getSupportedPlatforms = async () => {
-    const data = await fetchSupportedPlatforms()
-    supportedPlatforms.value = data as SupportedPlatform[]
-    loading.value = false
-  }
-
-  const formattedOptions = computed<Option[]>(() => {
-    return supportedPlatforms.value.map((platform: SupportedPlatform) => ({
-      ...platform,
-      nextPage: 'platform-auth-form'
-    })) as Option[]
-  })
-
-  const platformSelections = computed<SelectData[]>(() => [
-    {
-      title: t('ai.select_platform_to_authorize'),
-      hasDel: false,
-      options: formattedOptions.value
-    }
-  ])
-
-  watch(
-    currPage,
-    (val) => {
-      if (visible.value && val?.nextPage === 'platform-selection') {
-        getSupportedPlatforms()
-      }
-    },
-    {
-      immediate: true,
-      deep: true
-    }
-  )
-
-  const onSelect = async (option: Option) => {
-    const transformedData = {
-      ...toRaw(chatPayload.value),
-      authId: option.id,
-      platformName: option.name,
-      supportModels: option.supportModels
-    }
-    emits('update:chatPayload', transformedData)
-    emits('update:currPage', option)
-  }
-</script>
-
-<template>
-  <div class="platform-selection">
-    <a-spin :spinning="loading">
-      <select-menu :select-data="platformSelections" @select="onSelect" />
-    </a-spin>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-  .platform-selection {
-    height: 100%;
-    @include flexbox($direction: column, $justify: space-between);
-  }
-</style>
diff --git a/bigtop-manager-ui/src/components/chatbot/select-menu.vue b/bigtop-manager-ui/src/components/chatbot/select-menu.vue
deleted file mode 100644
index 335c66db..00000000
--- a/bigtop-manager-ui/src/components/chatbot/select-menu.vue
+++ /dev/null
@@ -1,157 +0,0 @@
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one
-  ~ or more contributor license agreements.  See the NOTICE file
-  ~ distributed with this work for additional information
-  ~ regarding copyright ownership.  The ASF licenses this file
-  ~ to you under the Apache License, Version 2.0 (the
-  ~ "License"); you may not use this file except in compliance
-  ~ with the License.  You may obtain a copy of the License at
-  ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing,
-  ~ software distributed under the License is distributed on an
-  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  ~ KIND, either express or implied.  See the License for the
-  ~ specific language governing permissions and limitations
-  ~ under the License.
--->
-<script setup lang="ts">
-  import { CloseOutlined } from '@ant-design/icons-vue'
-  import { toRefs } from 'vue'
-
-  export type Option = {
-    nextPage: string
-    id?: string | number
-    name?: string
-    [key: string]: any
-  }
-
-  export interface SelectData {
-    title?: string
-    isDeletable?: boolean
-    emptyOptionsText?: string
-    options?: Option[]
-  }
-
-  interface SelectBoxProps {
-    selectData?: SelectData[]
-  }
-
-  interface SelectBoxEmits {
-    (event: 'select', option: Option): void
-    (event: 'remove', option: Option): void
-  }
-
-  const props = defineProps<SelectBoxProps>()
-  const emits = defineEmits<SelectBoxEmits>()
-  const { selectData } = toRefs(props)
-
-  const onRemove = (option: Option) => {
-    emits('remove', option)
-  }
-  const onSelect = (option: Option) => {
-    emits('select', option)
-  }
-</script>
-
-<template>
-  <ul class="select">
-    <li v-for="(item, index) of selectData" :key="index" class="select-item">
-      <ul>
-        <label class="select-item-label">{{ item.title }}</label>
-        <slot name="select-custom-content">
-          <div v-if="!item.options || item.options.length == 0">
-            <slot name="empty-text">
-              <div class="select-item-empty">
-                {{ item.emptyOptionsText || $t('common.no_options') }}
-              </div>
-            </slot>
-          </div>
-          <template v-else>
-            <li
-              v-for="(option, idx) of item.options"
-              :key="idx"
-              class="select-item-option"
-              @click="onSelect(option)"
-            >
-              <span>
-                {{ option.name }}
-              </span>
-              <div
-                v-show="item.isDeletable"
-                :key="idx"
-                class="select-item-del"
-                @click.stop="onRemove(option)"
-              >
-                <CloseOutlined />
-              </div>
-            </li>
-          </template>
-        </slot>
-      </ul>
-    </li>
-  </ul>
-</template>
-
-<style lang="scss" scoped>
-  ul {
-    list-style: none;
-    margin: 0;
-    padding: 0;
-    box-sizing: border-box;
-
-    li {
-      padding: 4px 6px;
-      margin-bottom: 6px;
-      border-radius: 6px;
-    }
-  }
-
-  .select {
-    width: 100%;
-    height: 100%;
-
-    &-item {
-      margin-bottom: 20px;
-
-      &-label {
-        display: block;
-        margin-bottom: 10px;
-        font-weight: 500;
-      }
-
-      &-empty {
-        padding: 10px;
-        text-align: center;
-        color: #a9a9a9;
-      }
-
-      &-option {
-        @include flexbox($justify: space-between, $align: center);
-        border: 1px solid #c9c9c9;
-        cursor: pointer;
-
-        span {
-          flex: 1;
-          text-align: center;
-        }
-
-        .select-item-del {
-          transition: opacity 0.08s ease-in-out;
-          opacity: 0;
-          flex: 0 0 14px;
-        }
-
-        &:hover {
-          background-color: rgb(0, 0, 0, 0.06);
-
-          .select-item-del {
-            display: block;
-            opacity: 1;
-          }
-        }
-      }
-    }
-  }
-</style>
diff --git a/bigtop-manager-ui/src/components/chatbot/thread-selector.vue b/bigtop-manager-ui/src/components/chatbot/thread-selector.vue
deleted file mode 100644
index 11443fc2..00000000
--- a/bigtop-manager-ui/src/components/chatbot/thread-selector.vue
+++ /dev/null
@@ -1,157 +0,0 @@
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one
-  ~ or more contributor license agreements.  See the NOTICE file
-  ~ distributed with this work for additional information
-  ~ regarding copyright ownership.  The ASF licenses this file
-  ~ to you under the Apache License, Version 2.0 (the
-  ~ "License"); you may not use this file except in compliance
-  ~ with the License.  You may obtain a copy of the License at
-  ~
-  ~   http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing,
-  ~ software distributed under the License is distributed on an
-  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  ~ KIND, either express or implied.  See the License for the
-  ~ specific language governing permissions and limitations
-  ~ under the License.
--->
-<script setup lang="ts">
-  import SelectMenu from './select-menu.vue'
-  import useChatBot from '@/composables/use-chat-bot'
-  import {
-    toRefs,
-    computed,
-    h,
-    ref,
-    watchEffect,
-    toRaw,
-    onActivated
-  } from 'vue'
-  import { useI18n } from 'vue-i18n'
-  import { Modal } from 'ant-design-vue/es/components'
-  import { ExclamationCircleFilled } from '@ant-design/icons-vue/lib/icons'
-  import type { SelectData, Option } from './select-menu.vue'
-  import type {
-    ChatbotConfig,
-    ChatThread,
-    ChatThreadDelCondition
-  } from '@/api/chatbot/types'
-
-  interface PreChatProps {
-    visible: boolean
-    chatPayload: ChatbotConfig
-    currPage?: Option
-  }
-
-  const {
-    loading,
-    fetchChatThreads,
-    fetchCreateChatThread,
-    fetchDelChatThread
-  } = useChatBot()
-  const { t } = useI18n()
-  const props = defineProps<PreChatProps>()
-  const { currPage, visible, chatPayload } = toRefs(props)
-  const chatThreads = ref<ChatThread[]>([])
-  const emits = defineEmits(['update:currPage', 'update:chatPayload'])
-
-  const formattedOptions = computed<Option[]>(() => {
-    return chatThreads.value.map((v) => ({
-      ...v,
-      id: v.threadId,
-      name: t('ai.thread_name', [v.threadId]),
-      nextPage: 'chat-window'
-    }))
-  })
-
-  const chatThreadsSelectData = computed<SelectData[]>(() => {
-    const selectData = [
-      {
-        title: t('ai.select_thread_to_chat'),
-        isDeletable: true,
-        options: formattedOptions.value
-      },
-      {
-        title: t('ai.or_you_can'),
-        isDeletable: false,
-        options: [
-          {
-            nextPage: 'chat-window',
-            name: t('ai.create_new_thread')
-          }
-        ]
-      }
-    ]
-    return chatThreads.value.length === 10 ? [selectData[0]] : selectData
-  })
-
-  const getAllChatThreads = async () => {
-    const { authId, model } = chatPayload.value
-    const data = (await fetchChatThreads(
-      authId as string | number,
-      model as string
-    )) as ChatThread[]
-    chatThreads.value = data
-  }
-
-  watchEffect(async () => {
-    if (currPage.value?.nextPage === 'thread-selector' && visible.value) {
-      getAllChatThreads()
-    }
-  })
-
-  const onSelect = async (option: Option) => {
-    if (option.nextPage === 'chat-window' && option.id) {
-      emits('update:chatPayload', {
-        ...toRaw(chatPayload.value),
-        threadId: option.id,
-        threadName: option.name
-      })
-    } else {
-      const data = (await fetchCreateChatThread({
-        authId: chatPayload.value.authId as string | number,
-        model: chatPayload.value.model as string
-      })) as ChatThread
-      emits('update:chatPayload', {
-        ...toRaw(chatPayload.value),
-        ...data,
-        threadName: `Thread ${data.threadId}`
-      })
-    }
-    emits('update:currPage', option)
-  }
-
-  const onRemove = (option: Option) => {
-    Modal.confirm({
-      title: t('common.delete_confirm_title'),
-      icon: h(ExclamationCircleFilled),
-      content: t('common.delete_confirm_content', [option.name]),
-      async onOk() {
-        await fetchDelChatThread({
-          threadId: option.id,
-          authId: chatPayload.value.authId
-        } as ChatThreadDelCondition)
-        getAllChatThreads()
-      }
-    })
-  }
-
-  onActivated(() => {
-    chatThreads.value = []
-  })
-</script>
-
-<template>
-  <div>
-    <a-spin :spinning="loading">
-      <select-menu
-        :select-data="chatThreadsSelectData"
-        @remove="onRemove"
-        @select="onSelect"
-      />
-    </a-spin>
-  </div>
-</template>
-
-<style scoped></style>
diff --git a/bigtop-manager-ui/src/components/common/auto-form/index.vue b/bigtop-manager-ui/src/components/common/auto-form/index.vue
index 79c3046f..9d924006 100644
--- a/bigtop-manager-ui/src/components/common/auto-form/index.vue
+++ b/bigtop-manager-ui/src/components/common/auto-form/index.vue
@@ -149,20 +149,13 @@
     >
       <div v-for="item in props.formItems" :key="item.field">
         <slot :="{ item, state: formState }" :name="item.field">
-          <a-form-item
-            v-if="!props.hiddenItems.includes(item.field)"
-            v-bind="item.formItemProps"
-          >
+          <a-form-item v-if="!props.hiddenItems.includes(item.field)" v-bind="item.formItemProps">
             <!-- input -->
             <a-input
               v-if="item.type == 'input'"
               v-model:value="formState[item.field]"
               v-bind="item.controlProps"
-              :disabled="
-                disabledItems
-                  ? disabledItems.includes(item.field)
-                  : item.controlProps.disabled
-              "
+              :disabled="disabledItems ? disabledItems.includes(item.field) : item.controlProps.disabled"
               v-on="formItemEvents[item.field] || {}"
             />
 
@@ -171,11 +164,7 @@
               v-if="item.type == 'textarea'"
               v-model:value="formState[item.field]"
               v-bind="item.controlProps"
-              :disabled="
-                disabledItems
-                  ? disabledItems.includes(item.field)
-                  : item.controlProps.disabled
-              "
+              :disabled="disabledItems ? disabledItems.includes(item.field) : item.controlProps.disabled"
               v-on="formItemEvents[item.field] || {}"
             />
 
@@ -184,29 +173,15 @@
               v-if="item.type == 'select'"
               v-model:value="formState[item.field]"
               v-bind="item.controlProps"
-              :disabled="
-                disabledItems
-                  ? disabledItems.includes(item.field)
-                  : item.controlProps.disabled
-              "
+              :disabled="disabledItems ? disabledItems.includes(item.field) : item.controlProps.disabled"
               v-on="formItemEvents[item.field] || {}"
             >
               <a-select-option
-                v-for="(child, childIndex) in optionMap[item.field] ||
-                item.defaultOptionsMap ||
-                []"
+                v-for="(child, childIndex) in optionMap[item.field] || item.defaultOptionsMap || []"
                 :key="childIndex"
-                :value="
-                  typeof child === 'string'
-                    ? child
-                    : child[(item.fieldMap && item.fieldMap.value) || 'value']
-                "
+                :value="typeof child === 'string' ? child : child[(item.fieldMap && item.fieldMap.value) || 'value']"
               >
-                {{
-                  typeof child === 'string'
-                    ? child
-                    : child[(item.fieldMap && item.fieldMap.label) || 'label']
-                }}
+                {{ typeof child === 'string' ? child : child[(item.fieldMap && item.fieldMap.label) || 'label'] }}
               </a-select-option>
             </a-select>
 
@@ -215,21 +190,13 @@
               v-if="item.type == 'radio'"
               v-model:value="formState[item.field]"
               v-bind="item.controlProps"
-              :disabled="
-                disabledItems
-                  ? disabledItems.includes(item.field)
-                  : item.controlProps.disabled
-              "
+              :disabled="disabledItems ? disabledItems.includes(item.field) : item.controlProps.disabled"
               v-on="formItemEvents[item.field] || {}"
             >
               <a-radio
-                v-for="(child, childIndex) in optionMap[item.field] ||
-                item.defaultOptionsMap ||
-                []"
+                v-for="(child, childIndex) in optionMap[item.field] || item.defaultOptionsMap || []"
                 :key="childIndex"
-                :value="
-                  child[(item.fieldMap && item.fieldMap.value) || 'value']
-                "
+                :value="child[(item.fieldMap && item.fieldMap.value) || 'value']"
               >
                 {{ child[(item.fieldMap && item.fieldMap.label) || 'label'] }}
               </a-radio>
diff --git a/bigtop-manager-ui/src/components/common/auto-form/template.vue b/bigtop-manager-ui/src/components/common/auto-form/template.vue
index b42f7418..08512fbb 100644
--- a/bigtop-manager-ui/src/components/common/auto-form/template.vue
+++ b/bigtop-manager-ui/src/components/common/auto-form/template.vue
@@ -21,10 +21,7 @@
   import { computed, onMounted, onUnmounted, ref, watchPostEffect } from 'vue'
   import { usePngImage } from '@/utils/tools'
   import { useI18n } from 'vue-i18n'
-  import type {
-    FormItemState,
-    FormState
-  } from '@/components/common/auto-form/types'
+  import type { FormItemState, FormState } from '@/components/common/auto-form/types'
 
   const { t } = useI18n()
   const helper = usePngImage('helper')
diff --git a/bigtop-manager-ui/src/components/common/button-group/index.vue b/bigtop-manager-ui/src/components/common/button-group/index.vue
new file mode 100644
index 00000000..bc2b6d50
--- /dev/null
+++ b/bigtop-manager-ui/src/components/common/button-group/index.vue
@@ -0,0 +1,57 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+-->
+
+<script setup lang="ts">
+  import { toRefs } from 'vue'
+  import type { Props } from './types'
+
+  const props = withDefaults(defineProps<Props>(), {
+    i18n: '',
+    groupShape: 'circle',
+    groupType: 'text',
+    groups: () => {
+      return []
+    }
+  })
+
+  const { groups, groupShape, groupType } = toRefs(props)
+</script>
+
+<template>
+  <a-space>
+    <a-button
+      v-for="(item, idx) in groups"
+      :key="idx"
+      :disabled="item.disabled"
+      :shape="item.shape || groupShape || 'default'"
+      :type="item.type || groupType || 'default'"
+      :title="(item.tip && $t(`${$props.i18n}.${item.tip}`)) || (item.text && $t(`${$props.i18n}.${item.text}`))"
+      @click="item.clickEvent ? item.clickEvent(item) : () => {}"
+    >
+      <template #icon>
+        <slot name="icon" :item="item" />
+      </template>
+      <span v-if="item.text">
+        {{ item.text && $t(`${$props.i18n}.${item.text}`) }}
+      </span>
+    </a-button>
+  </a-space>
+</template>
+
+<style lang="scss" scoped></style>
diff --git a/bigtop-manager-ui/src/enums/state.ts b/bigtop-manager-ui/src/components/common/button-group/types.ts
similarity index 64%
copy from bigtop-manager-ui/src/enums/state.ts
copy to bigtop-manager-ui/src/components/common/button-group/types.ts
index d2096267..ae481660 100644
--- a/bigtop-manager-ui/src/enums/state.ts
+++ b/bigtop-manager-ui/src/components/common/button-group/types.ts
@@ -17,31 +17,23 @@
  * under the License.
  */
 
-export type MaintainState =
-  | 'Uninstalled'
-  | 'Installed'
-  | 'Maintained'
-  | 'Started'
-  | 'Stopped'
+type BtnType = 'primary' | 'ghost' | 'dashed' | 'link' | 'text' | 'default'
+type ShapeType = 'default' | 'circle' | 'round'
 
-export enum State {
-  Pending = '#1677ff',
-  Processing = '#1677fe',
-  Successful = '#52c41a',
-  Failed = '#ff4d4f',
-  Canceled = '#80868b'
+export interface GroupItem<T = string> {
+  icon?: string
+  tip?: string
+  text?: string
+  action?: T
+  type?: BtnType
+  shape?: ShapeType
+  disabled?: boolean
+  clickEvent?: (item?: GroupItem, ...args: any[]) => void
 }
 
-export enum CommonState {
-  normal = '#52c41a',
-  abnormal = '#ff4d4f',
-  maintained = '#d9d9d9'
-}
-
-export enum CurrState {
-  Installed,
-  Started,
-  Maintained,
-  Uninstalled,
-  Stopped
+export interface Props {
+  i18n: string
+  groups: GroupItem[]
+  groupType?: BtnType
+  groupShape?: ShapeType
 }
diff --git a/bigtop-manager-ui/src/components/common/index.ts b/bigtop-manager-ui/src/components/common/index.ts
index 61da3360..427e7ed2 100644
--- a/bigtop-manager-ui/src/components/common/index.ts
+++ b/bigtop-manager-ui/src/components/common/index.ts
@@ -20,9 +20,7 @@
 import type { App } from 'vue'
 
 const kebabToCamel = (kebab: string): string => {
-  return kebab
-    .replace(/-./g, (match) => match.charAt(1).toUpperCase())
-    .replace(/^./, (match) => match.toUpperCase())
+  return kebab.replace(/-./g, (match) => match.charAt(1).toUpperCase()).replace(/^./, (match) => match.toUpperCase())
 }
 
 const install = (app: App) => {
diff --git a/bigtop-manager-ui/src/components/common/markdown-view/index.vue b/bigtop-manager-ui/src/components/common/markdown-view/index.vue
index 573f6b46..15b72209 100644
--- a/bigtop-manager-ui/src/components/common/markdown-view/index.vue
+++ b/bigtop-manager-ui/src/components/common/markdown-view/index.vue
@@ -57,16 +57,9 @@
       const langTag = pre.querySelector('#language')
       const copyTag = pre.querySelector('#copy')
 
-      langTag!.textContent = (
-        code?.classList.value.replace('hljs language-', '') as string
-      ).toLowerCase()
-      copyTag &&
-        copyTag.removeEventListener('click', () =>
-          copyCode(code?.textContent || '')
-        )
-      copyTag?.addEventListener('click', () =>
-        copyCode(code?.textContent || '')
-      )
+      langTag!.textContent = (code?.classList.value.replace('hljs language-', '') as string).toLowerCase()
+      copyTag && copyTag.removeEventListener('click', () => copyCode(code?.textContent || ''))
+      copyTag?.addEventListener('click', () => copyCode(code?.textContent || ''))
     })
   }
 
@@ -99,12 +92,7 @@
 </script>
 
 <template>
-  <div
-    v-upgradeCodeBlock
-    v-dompurify-html="markdownContent"
-    class="markdown-body"
-  >
-  </div>
+  <div v-upgradeCodeBlock v-dompurify-html="markdownContent" class="markdown-body"> </div>
 </template>
 
 <style lang="scss" scoped></style>
diff --git a/bigtop-manager-ui/src/components/user-avatar/index.vue b/bigtop-manager-ui/src/components/user-avatar/index.vue
index b8778a0e..8e67e101 100644
--- a/bigtop-manager-ui/src/components/user-avatar/index.vue
+++ b/bigtop-manager-ui/src/components/user-avatar/index.vue
@@ -18,11 +18,7 @@
 -->
 
 <script setup lang="ts">
-  import {
-    UserOutlined,
-    ProfileOutlined,
-    LogoutOutlined
-  } from '@ant-design/icons-vue'
+  import { UserOutlined, ProfileOutlined, LogoutOutlined } from '@ant-design/icons-vue'
   import { message } from 'ant-design-vue'
   import { useUserStore } from '@/store/user'
   import { storeToRefs } from 'pinia'
diff --git a/bigtop-manager-ui/src/composables/use-chat-bot.ts b/bigtop-manager-ui/src/composables/use-chat-bot.ts
deleted file mode 100644
index feca96ee..00000000
--- a/bigtop-manager-ui/src/composables/use-chat-bot.ts
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { ref } from 'vue'
-import {
-  createChatThread,
-  delAuthorizedPlatform,
-  delChatThread,
-  getAuthorizedPlatforms,
-  getChatThreads,
-  getCredentialFormModelOfPlatform,
-  getSupportedPlatforms,
-  getThreadChatHistory,
-  validateAuthCredentials
-} from '@/api/chatbot/index'
-import type {
-  ChatThreadCondition,
-  AuthCredential,
-  ChatThreadDelCondition,
-  SendChatMessageCondition
-} from '@/api/chatbot/types'
-
-import { sendChatMessage } from '@/api/sse'
-import { AxiosProgressEvent, Canceler } from 'axios'
-import { useI18n } from 'vue-i18n'
-import { message } from 'ant-design-vue'
-
-const useChatBot = () => {
-  const { t } = useI18n()
-  const loading = ref(false)
-  const receiving = ref(false)
-  const checkLoading = ref(false)
-  const canceler = ref<Canceler>()
-  const messageReceiver = ref('')
-
-  function formatAuthCredentials<T extends Object>(
-    authFormData: T
-  ): AuthCredential[] {
-    const authCredentials: AuthCredential[] = []
-    for (const [key, value] of Object.entries(authFormData)) {
-      authCredentials.push({ key, value } as AuthCredential)
-    }
-    return authCredentials
-  }
-
-  async function fetchAuthorizedPlatforms() {
-    try {
-      loading.value = true
-      const data = await getAuthorizedPlatforms()
-      return Promise.resolve(data)
-    } catch (error) {
-      console.log('error :>> ', error)
-      return []
-    } finally {
-      loading.value = false
-    }
-  }
-
-  async function fetchSupportedPlatforms() {
-    try {
-      loading.value = true
-      const data = await getSupportedPlatforms()
-      return Promise.resolve(data)
-    } catch (error) {
-      console.log('error :>> ', error)
-      return []
-    } finally {
-      loading.value = false
-    }
-  }
-
-  async function fetchCredentialFormModelOfPlatform(
-    platformId: string | number
-  ) {
-    try {
-      loading.value = true
-      const data = await getCredentialFormModelOfPlatform(platformId)
-      return Promise.resolve(data)
-    } catch (error) {
-      console.log('error :>> ', error)
-      return []
-    } finally {
-      loading.value = false
-    }
-  }
-
-  async function testAuthPlatform<T extends Object>(
-    platformId: string | number,
-    authFormData: T
-  ) {
-    try {
-      checkLoading.value = true
-      const data = await validateAuthCredentials({
-        platformId,
-        authCredentials: formatAuthCredentials<T>(authFormData)
-      })
-      return Promise.resolve(data)
-    } catch (error) {
-      checkLoading.value = false
-      console.log('error :>> ', error)
-    }
-  }
-
-  async function fetchChatThreads(authId: string | number, model: string) {
-    try {
-      loading.value = true
-      const data = await getChatThreads({
-        model,
-        authId
-      })
-      return Promise.resolve(data)
-    } catch (error) {
-      console.log('error :>> ', error)
-      return []
-    } finally {
-      loading.value = false
-    }
-  }
-
-  async function fetchCreateChatThread(platformInfo: ChatThreadCondition) {
-    try {
-      loading.value = true
-      const data = await createChatThread(platformInfo)
-      return Promise.resolve(data)
-    } catch (error) {
-      console.log('error :>> ', error)
-    } finally {
-      loading.value = false
-    }
-  }
-
-  async function fetchThreadChatHistory(
-    authId: string | number,
-    threadId: string | number
-  ) {
-    try {
-      loading.value = true
-      const data = await getThreadChatHistory({ authId, threadId })
-      return Promise.resolve(data)
-    } catch (error) {
-      console.log('error :>> ', error)
-      return []
-    } finally {
-      loading.value = false
-    }
-  }
-
-  async function fetchSendChatMessage(reqparams: SendChatMessageCondition) {
-    try {
-      const { cancel, promise } = sendChatMessage(reqparams, onMessageReceive)
-      canceler.value = cancel
-      return promise.then(onMessageComplete)
-    } catch (error) {
-      console.log('error :>> ', error)
-    }
-  }
-
-  async function fetchDelAuthorizedPlatform(platformId: string | number) {
-    try {
-      loading.value = true
-      const data = await delAuthorizedPlatform(platformId)
-      message[`${data ? 'success' : 'error'}`](
-        data ? t('common.delete_success') : t('common.delete_fail')
-      )
-    } catch (error) {
-      console.log('error :>> ', error)
-    } finally {
-      loading.value = false
-    }
-  }
-  async function fetchDelChatThread(payload: ChatThreadDelCondition) {
-    try {
-      loading.value = true
-      const data = await delChatThread(payload)
-      message[`${data ? 'success' : 'error'}`](
-        data ? t('common.delete_success') : t('common.delete_fail')
-      )
-    } catch (error) {
-      console.log('error :>> ', error)
-    } finally {
-      loading.value = false
-    }
-  }
-
-  const onMessageReceive = ({ event }: AxiosProgressEvent) => {
-    messageReceiver.value = event.target.responseText
-      .split('data:')
-      .map((s: string) => {
-        return s.trimEnd()
-      })
-      .join('')
-  }
-
-  const onMessageComplete = () => {
-    const formatResultMsg = messageReceiver.value
-    messageReceiver.value = ''
-    cancelSseConnect()
-    return Promise.resolve({ message: formatResultMsg, state: true })
-  }
-
-  const cancelSseConnect = () => {
-    if (!canceler.value) {
-      return
-    }
-    canceler.value()
-  }
-
-  return {
-    messageReceiver,
-    loading,
-    receiving,
-    checkLoading,
-    fetchSupportedPlatforms,
-    fetchCredentialFormModelOfPlatform,
-    testAuthPlatform,
-    fetchCreateChatThread,
-    fetchChatThreads,
-    fetchAuthorizedPlatforms,
-    fetchThreadChatHistory,
-    fetchSendChatMessage,
-    fetchDelAuthorizedPlatform,
-    fetchDelChatThread
-  }
-}
-
-export default useChatBot
diff --git a/bigtop-manager-ui/src/components/common/index.ts b/bigtop-manager-ui/src/directives/index.ts
similarity index 55%
copy from bigtop-manager-ui/src/components/common/index.ts
copy to bigtop-manager-ui/src/directives/index.ts
index 61da3360..d6be88c2 100644
--- a/bigtop-manager-ui/src/components/common/index.ts
+++ b/bigtop-manager-ui/src/directives/index.ts
@@ -19,23 +19,36 @@
 
 import type { App } from 'vue'
 
-const kebabToCamel = (kebab: string): string => {
-  return kebab
-    .replace(/-./g, (match) => match.charAt(1).toUpperCase())
-    .replace(/^./, (match) => match.toUpperCase())
+interface ObserverElement extends Element {
+  cancelObserver?: MutationObserver
 }
 
 const install = (app: App) => {
-  const components = import.meta.glob('../../components/common/**/index.vue', {
-    eager: true
-  }) as any
-  for (const path in components) {
-    const component = components[path].default
-    const componentName = kebabToCamel(path.split('/')[1])
-    if (componentName) {
-      app.component(componentName, component)
+  app.directive('autoScroll', {
+    mounted(el: ObserverElement) {
+      const observerConfig = {
+        childList: true,
+        subtree: true,
+        characterDataOldValue: true,
+        characterData: true
+      }
+
+      const observer = new MutationObserver(() => {
+        const lastChild = el.lastElementChild
+        lastChild && lastChild.scrollIntoView({ behavior: 'smooth', block: 'end' })
+      })
+      observer.observe(el, observerConfig)
+
+      el.cancelObserver = observer
+    },
+
+    beforeUnmount(el: ObserverElement) {
+      if (el.cancelObserver) {
+        el.cancelObserver.disconnect()
+        delete el.cancelObserver
+      }
     }
-  }
+  })
 }
 
 export default {
diff --git a/bigtop-manager-ui/src/enums/route-exceptions.ts b/bigtop-manager-ui/src/enums/route-exceptions.ts
index fb1b16cb..d53482d7 100644
--- a/bigtop-manager-ui/src/enums/route-exceptions.ts
+++ b/bigtop-manager-ui/src/enums/route-exceptions.ts
@@ -21,5 +21,5 @@ export enum RouteExceptions {
   DEFAULT_ROUTE_NAME = 'Default',
   DYNAMIC_ROUTE_MATCH = ':cluster/:id',
   SPECIAL_ROUTE_NAME = 'Clusters',
-  SPECIAL_ROUTE_PATH = '/cluster-mange/clusters'
+  SPECIAL_ROUTE_PATH = '/cluster-manage/clusters'
 }
diff --git a/bigtop-manager-ui/src/enums/state.ts b/bigtop-manager-ui/src/enums/state.ts
index d2096267..003b044c 100644
--- a/bigtop-manager-ui/src/enums/state.ts
+++ b/bigtop-manager-ui/src/enums/state.ts
@@ -17,12 +17,7 @@
  * under the License.
  */
 
-export type MaintainState =
-  | 'Uninstalled'
-  | 'Installed'
-  | 'Maintained'
-  | 'Started'
-  | 'Stopped'
+export type MaintainState = 'Uninstalled' | 'Installed' | 'Maintained' | 'Started' | 'Stopped'
 
 export enum State {
   Pending = '#1677ff',
diff --git a/bigtop-manager-ui/src/layouts/footer.vue b/bigtop-manager-ui/src/layouts/footer.vue
index 8e36ae10..cb150ca1 100644
--- a/bigtop-manager-ui/src/layouts/footer.vue
+++ b/bigtop-manager-ui/src/layouts/footer.vue
@@ -25,9 +25,7 @@
   <a-layout-footer>
     <div class="copyright">
       {{ `Copyright ©2011-${dayjs().year()}` }}
-      <a href="https://www.apache.org" target="_blank">
-        The Apache Software Foundation
-      </a>
+      <a href="https://www.apache.org" target="_blank"> The Apache Software Foundation </a>
       . All rights reserved.
     </div>
   </a-layout-footer>
diff --git a/bigtop-manager-ui/src/layouts/header.vue b/bigtop-manager-ui/src/layouts/header.vue
index b34d5e6a..eeaf0090 100644
--- a/bigtop-manager-ui/src/layouts/header.vue
+++ b/bigtop-manager-ui/src/layouts/header.vue
@@ -21,6 +21,8 @@
   import { ref, toRefs } from 'vue'
   import SelectLang from '@/components/select-lang/index.vue'
   import UserAvatar from '@/components/user-avatar/index.vue'
+  import AiAssistant from '@/components/ai-assistant/index.vue'
+
   import { RouteRecordRaw } from 'vue-router'
 
   interface Props {
@@ -28,6 +30,10 @@
     headerMenus: RouteRecordRaw[]
   }
 
+  interface Emits {
+    (event: 'onHeaderClick', key: string): void
+  }
+
   const props = withDefaults(defineProps<Props>(), {
     headerSelectedKey: '',
     headerMenus: () => []
@@ -36,10 +42,17 @@
   const { headerSelectedKey, headerMenus } = toRefs(props)
   const githubUrl = import.meta.env.VITE_GITHUB_URL
   const spaceSize = ref(16)
-  const emits = defineEmits(['onHeaderClick'])
-  const onHeaderClick = ({ key }: any) => {
+  const aiAssistantRef = ref<InstanceType<typeof AiAssistant> | null>(null)
+
+  const emits = defineEmits<Emits>()
+
+  const handleHeaderSelect = ({ key }: any) => {
     emits('onHeaderClick', key)
   }
+
+  const handleCommunication = () => {
+    aiAssistantRef.value?.controlVisible()
+  }
 </script>
 
 <template>
@@ -48,12 +61,7 @@
       <svg-icon name="bm_logo" />
     </h1>
     <div class="header-menu">
-      <a-menu
-        :selected-keys="[headerSelectedKey]"
-        theme="dark"
-        mode="horizontal"
-        @select="onHeaderClick"
-      >
+      <a-menu :selected-keys="[headerSelectedKey]" theme="dark" mode="horizontal" @select="handleHeaderSelect">
         <a-menu-item v-for="route of headerMenus" :key="route.path">
           {{ $t(route.meta?.title || '') }}
         </a-menu-item>
@@ -62,7 +70,7 @@
     <div class="header-right common-layout">
       <a-space :size="spaceSize">
         <user-avatar />
-        <div class="header-item">
+        <div class="header-item" @click="handleCommunication">
           <svg-icon name="communication" />
         </div>
         <select-lang />
@@ -76,6 +84,7 @@
         </div>
       </a-space>
     </div>
+    <ai-assistant ref="aiAssistantRef" />
   </a-layout-header>
 </template>
 
@@ -86,7 +95,7 @@
   }
   .header {
     @include flexbox($justify: space-between, $align: center);
-    padding-inline: 0 16px;
+    padding-inline: 0 $space-md;
     height: $layout-header-height;
     .header-menu {
       flex: 1;
diff --git a/bigtop-manager-ui/src/layouts/index.vue b/bigtop-manager-ui/src/layouts/index.vue
index b769b149..7c70c6dc 100644
--- a/bigtop-manager-ui/src/layouts/index.vue
+++ b/bigtop-manager-ui/src/layouts/index.vue
@@ -30,8 +30,7 @@
   const userStore = useUserStore()
   const menuStore = useMenuStore()
   const clusterStore = useClusterStore()
-  const { headerSelectedKey, headerMenus, siderMenuSelectedKey, siderMenus } =
-    storeToRefs(menuStore)
+  const { headerSelectedKey, headerMenus, siderMenuSelectedKey, siderMenus } = storeToRefs(menuStore)
 
   onMounted(async () => {
     userStore.getUserInfo()
diff --git a/bigtop-manager-ui/src/layouts/sider.vue b/bigtop-manager-ui/src/layouts/sider.vue
index ac6ab902..9ace4338 100644
--- a/bigtop-manager-ui/src/layouts/sider.vue
+++ b/bigtop-manager-ui/src/layouts/sider.vue
@@ -42,9 +42,7 @@
   const toggleActivatedIcon = (menuItem: MenuItem) => {
     const { key, icon } = menuItem
     if (menuStore.isDynamicRouteMatched) {
-      return key === RouteExceptions.SPECIAL_ROUTE_PATH
-        ? `${icon}_activated`
-        : icon
+      return key === RouteExceptions.SPECIAL_ROUTE_PATH ? `${icon}_activated` : icon
     } else {
       return key === siderMenuSelectedKey.value ? `${icon}_activated` : icon
     }
@@ -61,17 +59,10 @@
 
 <template>
   <a-layout-sider class="sider">
-    <a-menu
-      :selected-keys="[siderMenuSelectedKey]"
-      mode="inline"
-      @select="onSiderClick"
-    >
+    <a-menu :selected-keys="[siderMenuSelectedKey]" mode="inline" @select="onSiderClick">
       <template v-for="menuItem in siderMenus" :key="menuItem.key">
         <a-sub-menu
-          v-if="
-            menuItem.children &&
-            menuItem.name === RouteExceptions.SPECIAL_ROUTE_NAME
-          "
+          v-if="menuItem.children && menuItem.name === RouteExceptions.SPECIAL_ROUTE_NAME"
           :key="menuItem.key"
         >
           <template #icon>
diff --git a/bigtop-manager-ui/src/components/common/index.ts b/bigtop-manager-ui/src/locales/en_US/ai-assistant.ts
similarity index 52%
copy from bigtop-manager-ui/src/components/common/index.ts
copy to bigtop-manager-ui/src/locales/en_US/ai-assistant.ts
index 61da3360..085c573b 100644
--- a/bigtop-manager-ui/src/components/common/index.ts
+++ b/bigtop-manager-ui/src/locales/en_US/ai-assistant.ts
@@ -16,28 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-import type { App } from 'vue'
-
-const kebabToCamel = (kebab: string): string => {
-  return kebab
-    .replace(/-./g, (match) => match.charAt(1).toUpperCase())
-    .replace(/^./, (match) => match.toUpperCase())
-}
-
-const install = (app: App) => {
-  const components = import.meta.glob('../../components/common/**/index.vue', {
-    eager: true
-  }) as any
-  for (const path in components) {
-    const component = components[path].default
-    const componentName = kebabToCamel(path.split('/')[1])
-    if (componentName) {
-      app.component(componentName, component)
-    }
-  }
-}
-
 export default {
-  install
+  history: 'History',
+  ai_assistant: 'AI assistant',
+  new_chat: 'New Chat',
+  full_screen: 'Full screen',
+  exit_screen: 'Exit screen',
+  delete_confirm: 'Are you sure you want to delete this thread?',
+  show_only10_records: 'Display only 10 records',
+  message_cannot_be_empty: 'Message cannot be empty',
+  enter_question: 'Please enter your question',
+  greeting: 'Hello, I am your AI assistant',
+  help: 'How can I assist you today?',
+  bigtop_manager: 'What is Bigtop Manager?',
+  can_do_for_you: 'What can you do for me?',
+  big_data_news: 'What interesting things are happening in the field of Big Data?',
+  ai_disabled_msg: 'Sorry, the large model is not set up, so the AI assistant is not available.',
+  go_set_up: 'Go to set it up'
 }
diff --git a/bigtop-manager-ui/src/locales/en_US/common.ts b/bigtop-manager-ui/src/locales/en_US/common.ts
index 0800f669..7765f2ea 100644
--- a/bigtop-manager-ui/src/locales/en_US/common.ts
+++ b/bigtop-manager-ui/src/locales/en_US/common.ts
@@ -25,6 +25,7 @@ export default {
   action: 'Action',
   status: 'Status',
   edit: 'Edit',
+  rename: 'Rename',
   delete: 'Delete',
   disable: 'Disable',
   enable: 'Enable',
@@ -68,10 +69,10 @@ export default {
   delete_success: 'Delete successfully !',
   delete_fail: 'Delete failed!',
   delete_confirm_title: 'Delete Confirm',
-  delete_confirm_content:
-    'Delete will not be recoverable, are you sure you want to delete {0} ?',
+  delete_confirm_content: 'Delete will not be recoverable, are you sure you want to delete {0} ?',
   no_options: 'No options',
   loading_text_verifying: 'Verifying',
   enter_error: 'Please enter {0}',
-  select_error: 'Please select {0}'
+  select_error: 'Please select {0}',
+  delete_msg: 'Are you sure you want to delete this?'
 }
diff --git a/bigtop-manager-ui/src/locales/en_US/index.ts b/bigtop-manager-ui/src/locales/en_US/index.ts
index 01477fd5..804d309c 100644
--- a/bigtop-manager-ui/src/locales/en_US/index.ts
+++ b/bigtop-manager-ui/src/locales/en_US/index.ts
@@ -22,11 +22,13 @@ import menu from '@/locales/en_US/menu.ts'
 import login from '@/locales/en_US/login'
 import user from '@/locales/en_US/user.ts'
 import llmConfig from '@/locales/en_US/llm-config.ts'
+import aiAssistant from '@/locales/en_US/ai-assistant.ts'
 
 export default {
   common,
   menu,
   login,
   user,
-  llmConfig
+  llmConfig,
+  aiAssistant
 }
diff --git a/bigtop-manager-ui/src/locales/en_US/llm-config.ts b/bigtop-manager-ui/src/locales/en_US/llm-config.ts
index 1718918b..c23e35f1 100644
--- a/bigtop-manager-ui/src/locales/en_US/llm-config.ts
+++ b/bigtop-manager-ui/src/locales/en_US/llm-config.ts
@@ -26,6 +26,5 @@ export default {
   name: 'Name',
   platform_name: 'Platform',
   model: 'Model',
-  desc: 'Remark',
-  delete_authorization: 'Are you sure delete this authorization ?'
+  desc: 'Remark'
 }
diff --git a/bigtop-manager-ui/src/components/common/index.ts b/bigtop-manager-ui/src/locales/zh_CN/ai-assistant.ts
similarity index 53%
copy from bigtop-manager-ui/src/components/common/index.ts
copy to bigtop-manager-ui/src/locales/zh_CN/ai-assistant.ts
index 61da3360..1df697c4 100644
--- a/bigtop-manager-ui/src/components/common/index.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/ai-assistant.ts
@@ -16,28 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-import type { App } from 'vue'
-
-const kebabToCamel = (kebab: string): string => {
-  return kebab
-    .replace(/-./g, (match) => match.charAt(1).toUpperCase())
-    .replace(/^./, (match) => match.toUpperCase())
-}
-
-const install = (app: App) => {
-  const components = import.meta.glob('../../components/common/**/index.vue', {
-    eager: true
-  }) as any
-  for (const path in components) {
-    const component = components[path].default
-    const componentName = kebabToCamel(path.split('/')[1])
-    if (componentName) {
-      app.component(componentName, component)
-    }
-  }
-}
-
 export default {
-  install
+  history: '历史记录',
+  ai_assistant: 'AI 助理',
+  new_chat: '新聊天',
+  delete_confirm: '确认要删除聊天吗?',
+  full_screen: '窗口全屏',
+  exit_screen: '退出全屏',
+  show_only10_records: '仅展示 10 条历史记录',
+  message_cannot_be_empty: '消息不能为空',
+  enter_question: '请输入你的问题',
+  greeting: '你好,我是你的 AI 助理',
+  help: '请问有什么可以帮助你的吗?',
+  bigtop_manager: '什么是Bigtop Manager?',
+  can_do_for_you: '你可以为我做什么?',
+  big_data_news: '大数据领域最近有什么有趣的事?',
+  ai_disabled_msg: '抱歉,你还没有配置大模型,无法使用 AI 助理',
+  go_set_up: '前往配置'
 }
diff --git a/bigtop-manager-ui/src/locales/zh_CN/common.ts b/bigtop-manager-ui/src/locales/zh_CN/common.ts
index 6a189575..65cea43c 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/common.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/common.ts
@@ -26,6 +26,7 @@ export default {
   status: '状态',
   edit: '编辑',
   delete: '删除',
+  rename: '重命名',
   disable: '停用',
   enable: '使用',
   submit: '提交',
@@ -72,5 +73,6 @@ export default {
   no_options: '暂无选项',
   loading_text_verifying: '校验中',
   enter_error: '请输入{0}',
-  select_error: '请选择{0}'
+  select_error: '请选择{0}',
+  delete_msg: '确认要删除该记录吗?'
 }
diff --git a/bigtop-manager-ui/src/locales/zh_CN/index.ts b/bigtop-manager-ui/src/locales/zh_CN/index.ts
index bf381910..2cc7eeb3 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/index.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/index.ts
@@ -22,11 +22,13 @@ import menu from '@/locales/zh_CN/menu.ts'
 import login from '@/locales/zh_CN/login'
 import user from '@/locales/zh_CN/user.ts'
 import llmConfig from '@/locales/zh_CN/llm-config.ts'
+import aiAssistant from '@/locales/zh_CN/ai-assistant.ts'
 
 export default {
   common,
   menu,
   login,
   user,
-  llmConfig
+  llmConfig,
+  aiAssistant
 }
diff --git a/bigtop-manager-ui/src/locales/zh_CN/llm-config.ts b/bigtop-manager-ui/src/locales/zh_CN/llm-config.ts
index 778e6741..04facdd0 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/llm-config.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/llm-config.ts
@@ -26,6 +26,5 @@ export default {
   name: '名字',
   platform_name: '平台',
   model: '模型',
-  desc: '备注',
-  delete_authorization: '确认要删除授权吗 ?'
+  desc: '备注'
 }
diff --git a/bigtop-manager-ui/src/main.ts b/bigtop-manager-ui/src/main.ts
index f3534cbb..528bf2c3 100644
--- a/bigtop-manager-ui/src/main.ts
+++ b/bigtop-manager-ui/src/main.ts
@@ -22,6 +22,8 @@ import App from './App.vue'
 import plugins from '@/plugins'
 import 'ant-design-vue/dist/reset.css'
 import 'virtual:svg-icons-register'
+import '@/styles/scrollbar.scss'
+import '@/styles/marked.scss'
 
 const app = createApp(App)
 app.use(plugins, { antdMessageMaxCount: 1 })
diff --git a/bigtop-manager-ui/src/pages/cluster-mange/cluster/create.vue b/bigtop-manager-ui/src/pages/cluster-manage/cluster/create.vue
similarity index 100%
rename from bigtop-manager-ui/src/pages/cluster-mange/cluster/create.vue
rename to bigtop-manager-ui/src/pages/cluster-manage/cluster/create.vue
diff --git a/bigtop-manager-ui/src/pages/cluster-mange/cluster/detail.vue b/bigtop-manager-ui/src/pages/cluster-manage/cluster/detail.vue
similarity index 100%
rename from bigtop-manager-ui/src/pages/cluster-mange/cluster/detail.vue
rename to bigtop-manager-ui/src/pages/cluster-manage/cluster/detail.vue
diff --git a/bigtop-manager-ui/src/pages/cluster-mange/cluster/index.vue b/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
similarity index 100%
rename from bigtop-manager-ui/src/pages/cluster-mange/cluster/index.vue
rename to bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
diff --git a/bigtop-manager-ui/src/pages/cluster-mange/components/index.vue b/bigtop-manager-ui/src/pages/cluster-manage/components/index.vue
similarity index 100%
rename from bigtop-manager-ui/src/pages/cluster-mange/components/index.vue
rename to bigtop-manager-ui/src/pages/cluster-manage/components/index.vue
diff --git a/bigtop-manager-ui/src/pages/cluster-mange/hosts/create.vue b/bigtop-manager-ui/src/pages/cluster-manage/hosts/create.vue
similarity index 100%
rename from bigtop-manager-ui/src/pages/cluster-mange/hosts/create.vue
rename to bigtop-manager-ui/src/pages/cluster-manage/hosts/create.vue
diff --git a/bigtop-manager-ui/src/pages/cluster-mange/hosts/index.vue b/bigtop-manager-ui/src/pages/cluster-manage/hosts/index.vue
similarity index 100%
rename from bigtop-manager-ui/src/pages/cluster-mange/hosts/index.vue
rename to bigtop-manager-ui/src/pages/cluster-manage/hosts/index.vue
diff --git a/bigtop-manager-ui/src/pages/cluster-mange/infrastructures/index.vue b/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue
similarity index 100%
rename from bigtop-manager-ui/src/pages/cluster-mange/infrastructures/index.vue
rename to bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue
diff --git a/bigtop-manager-ui/src/pages/login/index.vue b/bigtop-manager-ui/src/pages/login/index.vue
index 1c291c4f..02082c7d 100644
--- a/bigtop-manager-ui/src/pages/login/index.vue
+++ b/bigtop-manager-ui/src/pages/login/index.vue
@@ -82,21 +82,13 @@
         <div class="login-body">
           <!-- 登录框主体左侧 -->
           <div class="login-body-left">
-            <img
-              class="login-body-left-img"
-              src="@/assets/images/login.png"
-              alt="login"
-            />
+            <img class="login-body-left-img" src="@/assets/images/login.png" alt="login" />
           </div>
           <a-divider class="login-body-divider m-0" type="vertical" />
           <!-- 登录框主体右侧 -->
           <div class="login-body-right">
             <div class="login-body-right-tips">{{ $t('login.tips') }}</div>
-            <a-form
-              ref="formRef"
-              class="login-body-right-form"
-              :model="loginModel"
-            >
+            <a-form ref="formRef" class="login-body-right-form" :model="loginModel">
               <a-tabs v-model:activeKey="loginModel.type" centered>
                 <a-tab-pane key="account" :tab="$t('login.tab_account')" />
               </a-tabs>
@@ -149,13 +141,7 @@
                   {{ $t('login.remember_me') }}
                 </a-checkbox>
               </div>
-              <a-button
-                type="primary"
-                block
-                :loading="submitLoading"
-                size="large"
-                @click="submit"
-              >
+              <a-button type="primary" block :loading="submitLoading" size="large" @click="submit">
                 {{ $t('login.submit') }}
               </a-button>
             </a-form>
@@ -165,8 +151,7 @@
     </div>
     <div class="copyright">
       Copyright ©2011–2023
-      <a href="https://www.apache.org">The Apache Software Foundation</a>. All
-      rights reserved.
+      <a href="https://www.apache.org">The Apache Software Foundation</a>. All rights reserved.
     </div>
   </div>
 </template>
@@ -268,11 +253,7 @@
           }
 
           .login-body-right {
-            @include flexbox(
-              $direction: column,
-              $justify: center,
-              $align: center
-            );
+            @include flexbox($direction: column, $justify: center, $align: center);
             width: 335px;
             padding: 0 1.25rem;
 
diff --git a/bigtop-manager-ui/src/pages/system-mange/llm-config/components/add-llm-item.vue b/bigtop-manager-ui/src/pages/system-manage/llm-config/components/add-llm-item.vue
similarity index 79%
rename from bigtop-manager-ui/src/pages/system-mange/llm-config/components/add-llm-item.vue
rename to bigtop-manager-ui/src/pages/system-manage/llm-config/components/add-llm-item.vue
index 2e632ee6..ba5c1e46 100644
--- a/bigtop-manager-ui/src/pages/system-mange/llm-config/components/add-llm-item.vue
+++ b/bigtop-manager-ui/src/pages/system-manage/llm-config/components/add-llm-item.vue
@@ -45,32 +45,15 @@
   const open = ref(false)
   const mode = ref<keyof typeof Mode>('ADD')
   const autoFormRef = ref<Comp.AutoFormInstance | null>(null)
-  const disableFormKeys = shallowRef(['platformId', 'model'])
-  const {
-    loading,
-    loadingTest,
-    currPlatform,
-    formKeys,
-    platforms,
-    isDisabled,
-    formCredentials,
-    supportModels
-  } = storeToRefs(llmConfigStore)
+  const disableFormKeys = shallowRef(['platformId'])
+  const { loading, loadingTest, currPlatform, formKeys, platforms, isDisabled, formCredentials, supportModels } =
+    storeToRefs(llmConfigStore)
 
   const isEdit = computed(() => mode.value === 'EDIT')
-  const disabledItems = computed(() =>
-    isEdit.value ? [...disableFormKeys.value, ...formKeys.value] : []
-  )
+  const disabledItems = computed(() => (isEdit.value ? [...disableFormKeys.value, ...formKeys.value] : []))
   const formItems = computed((): FormItemState[] => {
-    const newFormItems =
-      formCredentials.value?.map((v) =>
-        createNewFormItem('input', v.name, v.displayName)
-      ) ?? []
-    return [
-      ...formItemConfig.value.slice(0, 2),
-      ...newFormItems,
-      ...formItemConfig.value.slice(2)
-    ]
+    const newFormItems = formCredentials.value?.map((v) => createNewFormItem('input', v.name, v.displayName)) ?? []
+    return [...formItemConfig.value.slice(0, 2), ...newFormItems, ...formItemConfig.value.slice(2)]
   })
 
   watchEffect(() => {
@@ -90,15 +73,17 @@
     currPlatform.value = payload ?? currPlatform.value
     await llmConfigStore.getPlatforms()
     bindPropToFormItems()
-    isEdit.value && llmConfigStore.getAuthPlatformDetail()
+    if (isEdit.value) {
+      await llmConfigStore.getPlatformCredentials()
+      autoFormRef?.value?.setOptions('model', supportModels.value || [])
+      llmConfigStore.getAuthPlatformDetail()
+    }
   }
 
   const handleOk = async () => {
     const validate = await autoFormRef.value?.getFormValidation()
     if (!validate) return
-    const api = isEdit.value
-      ? llmConfigStore.updateAuthPlatform
-      : llmConfigStore.addAuthorizedPlatform
+    const api = isEdit.value ? llmConfigStore.updateAuthPlatform : llmConfigStore.addAuthorizedPlatform
     const success = await api()
     if (success) {
       const text = isEdit.value ? 'common.update_success' : 'common.created'
@@ -155,12 +140,7 @@
       <template #footer>
         <footer>
           <a-space size="middle">
-            <a-button
-              :disabled="loading"
-              :loading="loadingTest"
-              type="primary"
-              @click="handleTest"
-            >
+            <a-button :disabled="loading" :loading="loadingTest" type="primary" @click="handleTest">
               {{ $t('llmConfig.test') }}
             </a-button>
           </a-space>
@@ -168,12 +148,7 @@
             <a-button :disabled="isDisabled" @click="handleCancel">
               {{ $t('common.cancel') }}
             </a-button>
-            <a-button
-              :disabled="loadingTest"
-              :loading="loading"
-              type="primary"
-              @click="handleOk"
-            >
+            <a-button :disabled="loadingTest" :loading="loading" type="primary" @click="handleOk">
               {{ $t('common.confirm') }}
             </a-button>
           </a-space>
diff --git a/bigtop-manager-ui/src/pages/system-mange/llm-config/components/llm-item.vue b/bigtop-manager-ui/src/pages/system-manage/llm-config/components/llm-item.vue
similarity index 92%
rename from bigtop-manager-ui/src/pages/system-mange/llm-config/components/llm-item.vue
rename to bigtop-manager-ui/src/pages/system-manage/llm-config/components/llm-item.vue
index e15f52b4..46269893 100644
--- a/bigtop-manager-ui/src/pages/system-mange/llm-config/components/llm-item.vue
+++ b/bigtop-manager-ui/src/pages/system-manage/llm-config/components/llm-item.vue
@@ -141,9 +141,7 @@
   const currStatus = computed(() => llmConfig.value?.status)
   const actionKeysSet = computed(() => new Set(llmStatus.value?.actionKeys))
   const ellipsis = computed(() => (llmConfig.value ? true : false))
-  const llmStatus = computed(() =>
-    llmStatusConfig.value.find(({ status }) => status === currStatus.value)
-  )
+  const llmStatus = computed(() => llmStatusConfig.value.find(({ status }) => status === currStatus.value))
   const llmActions = computed(() => {
     return extraActions.value
       .filter((item) => actionKeysSet.value.has(item.key))
@@ -153,14 +151,12 @@
       }))
   })
 
-  const getActionDisabled = (key: ActionKeys): boolean =>
-    isDisable(key) || isEnable(key)
+  const getActionDisabled = (key: ActionKeys): boolean => isDisable(key) || isEnable(key)
 
   const isDisable = (key: ActionKeys): boolean =>
     currStatus.value === AuthPlatformStatus.UNAVAILABLE && key === 'ENABLE'
 
-  const isEnable = (key: ActionKeys): boolean =>
-    currStatus.value === AuthPlatformStatus.ACTIVE && key === 'DELETE'
+  const isEnable = (key: ActionKeys): boolean => currStatus.value === AuthPlatformStatus.ACTIVE && key === 'DELETE'
 
   const handleCreateLlmConfig = () => {
     emits('createLlmConfig')
@@ -217,11 +213,7 @@
           </a-dropdown>
         </div>
         <div class="llm-card-body">
-          <a-typography-paragraph
-            v-for="{ label, code } in llmDescriptions"
-            :key="code"
-            class="llm-card-desc"
-          >
+          <a-typography-paragraph v-for="{ label, code } in llmDescriptions" :key="code" class="llm-card-desc">
             <a-typography-text>{{ $t(label) }}</a-typography-text>
             <a-typography-paragraph
               type="secondary"
@@ -236,10 +228,7 @@
     <template v-else>
       <div class="llm-card-action" @click="handleCreateLlmConfig">
         <svg-icon name="plus_dark" />
-        <a-typography-text
-          type="secondary"
-          :content="$t('llmConfig.add_authorization')"
-        />
+        <a-typography-text type="secondary" :content="$t('llmConfig.add_authorization')" />
       </div>
     </template>
   </div>
diff --git a/bigtop-manager-ui/src/pages/system-mange/llm-config/index.vue b/bigtop-manager-ui/src/pages/system-manage/llm-config/index.vue
similarity index 93%
rename from bigtop-manager-ui/src/pages/system-mange/llm-config/index.vue
rename to bigtop-manager-ui/src/pages/system-manage/llm-config/index.vue
index 9f1b193b..880ce7bb 100644
--- a/bigtop-manager-ui/src/pages/system-mange/llm-config/index.vue
+++ b/bigtop-manager-ui/src/pages/system-manage/llm-config/index.vue
@@ -23,10 +23,7 @@
   import { useLlmConfigStore } from '@/store/llm-config/index'
   import { message, Modal } from 'ant-design-vue'
   import { useI18n } from 'vue-i18n'
-  import LlmItem, {
-    type ActionKeys,
-    type ExtraItem
-  } from './components/llm-item.vue'
+  import LlmItem, { type ActionKeys, type ExtraItem } from './components/llm-item.vue'
   import type { AuthorizedPlatform } from '@/api/llm-config/types'
   import addLlmItem from './components/add-llm-item.vue'
 
@@ -73,7 +70,7 @@
 
   const handleDeleteLlmConfig = (authId: string | number) => {
     Modal.confirm({
-      title: t('llmConfig.delete_authorization'),
+      title: t('common.delete_msg'),
       async onOk() {
         const success = await llmConfigStore.deleteAuthPlatform(authId)
         if (success) {
@@ -104,10 +101,7 @@
         />
         <llm-item :is-config="false" @create-llm-config="createLlmConfig" />
       </div>
-      <add-llm-item
-        ref="addLlmItemRef"
-        @on-ok="llmConfigStore.getAuthorizedPlatforms"
-      />
+      <add-llm-item ref="addLlmItemRef" @on-ok="llmConfigStore.getAuthorizedPlatforms" />
     </div>
   </a-spin>
 </template>
diff --git a/bigtop-manager-ui/src/pages/user/profile/index.vue b/bigtop-manager-ui/src/pages/user/profile/index.vue
index 6395b7be..2323e5d2 100644
--- a/bigtop-manager-ui/src/pages/user/profile/index.vue
+++ b/bigtop-manager-ui/src/pages/user/profile/index.vue
@@ -102,12 +102,7 @@
       @cancel="cancelEdit"
     >
       <br />
-      <a-form
-        ref="formRef"
-        name="profileForm"
-        :model="editUser"
-        layout="vertical"
-      >
+      <a-form ref="formRef" name="profileForm" :model="editUser" layout="vertical">
         <a-form-item
           :label="$t('user.nickname')"
           name="nickname"
diff --git a/bigtop-manager-ui/src/plugins/index.ts b/bigtop-manager-ui/src/plugins/index.ts
index d802617f..7ece92ec 100644
--- a/bigtop-manager-ui/src/plugins/index.ts
+++ b/bigtop-manager-ui/src/plugins/index.ts
@@ -23,6 +23,7 @@ import pinia from '@/store'
 import i18n from '@/locales'
 import Antd, { message } from 'ant-design-vue'
 import components from '@/components/common'
+import directives from '@/directives'
 import VueDOMPurifyHTML from 'vue-dompurify-html'
 interface PluginOptions {
   antdMessageMaxCount: number
@@ -34,6 +35,7 @@ export default {
     app.use(router)
     app.use(pinia)
     app.use(i18n)
+    app.use(directives)
     app.use(components)
     app.use(VueDOMPurifyHTML) // xss defense
     message.config({ maxCount: options.antdMessageMaxCount })
diff --git a/bigtop-manager-ui/src/router/routes/index.ts b/bigtop-manager-ui/src/router/routes/index.ts
index 86d5908b..2b1e70df 100644
--- a/bigtop-manager-ui/src/router/routes/index.ts
+++ b/bigtop-manager-ui/src/router/routes/index.ts
@@ -24,8 +24,7 @@ const dynamicRoutesFiles = import.meta.glob('./modules/**/*.ts', {
   eager: true
 })
 
-export const dynamicRoutes: RouteRecordRaw[] =
-  mergeRouteModules(dynamicRoutesFiles)
+export const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRoutesFiles)
 
 const routes: RouteRecordRaw[] = [
   {
@@ -34,7 +33,7 @@ const routes: RouteRecordRaw[] = [
   },
   {
     path: '/',
-    redirect: '/cluster-mange/'
+    redirect: '/cluster-manage/'
   },
   ...dynamicRoutes,
   {
diff --git a/bigtop-manager-ui/src/router/routes/modules/clusters.ts b/bigtop-manager-ui/src/router/routes/modules/clusters.ts
index d9223228..32fcf6c8 100644
--- a/bigtop-manager-ui/src/router/routes/modules/clusters.ts
+++ b/bigtop-manager-ui/src/router/routes/modules/clusters.ts
@@ -23,7 +23,7 @@ import pageView from '@/layouts/index.vue'
 
 const routes: RouteRecordRaw[] = [
   {
-    path: '/cluster-mange',
+    path: '/cluster-manage',
     component: pageView,
     redirect: RouteExceptions.SPECIAL_ROUTE_PATH,
     meta: {
@@ -50,7 +50,7 @@ const routes: RouteRecordRaw[] = [
           {
             name: 'ClusterDetail',
             path: RouteExceptions.DYNAMIC_ROUTE_MATCH,
-            component: () => import('@/pages/cluster-mange/cluster/index.vue'),
+            component: () => import('@/pages/cluster-manage/cluster/index.vue'),
             meta: {
               hidden: true
             }
@@ -58,7 +58,7 @@ const routes: RouteRecordRaw[] = [
           {
             name: 'ClusterCreate',
             path: 'create',
-            component: () => import('@/pages/cluster-mange/cluster/create.vue'),
+            component: () => import('@/pages/cluster-manage/cluster/create.vue'),
             meta: {
               hidden: true
             }
@@ -68,8 +68,7 @@ const routes: RouteRecordRaw[] = [
       {
         name: 'Infrastructures',
         path: 'infrastructures',
-        component: () =>
-          import('@/pages/cluster-mange/infrastructures/index.vue'),
+        component: () => import('@/pages/cluster-manage/infrastructures/index.vue'),
         meta: {
           icon: 'infrastructures',
           title: 'menu.infra'
@@ -78,7 +77,7 @@ const routes: RouteRecordRaw[] = [
       {
         name: 'Components',
         path: 'components',
-        component: () => import('@/pages/cluster-mange/components/index.vue'),
+        component: () => import('@/pages/cluster-manage/components/index.vue'),
         meta: {
           icon: 'components',
           title: 'menu.component'
@@ -87,7 +86,7 @@ const routes: RouteRecordRaw[] = [
       {
         name: 'Hosts',
         path: 'hosts',
-        redirect: '/cluster-mange/hosts/list',
+        redirect: '/cluster-manage/hosts/list',
         meta: {
           icon: 'hosts',
           title: 'menu.host'
@@ -96,19 +95,19 @@ const routes: RouteRecordRaw[] = [
           {
             name: 'List',
             path: 'list',
-            component: () => import('@/pages/cluster-mange/hosts/index.vue'),
+            component: () => import('@/pages/cluster-manage/hosts/index.vue'),
             meta: {
               hidden: true,
-              activeMenu: '/cluster-mange/hosts'
+              activeMenu: '/cluster-manage/hosts'
             }
           },
           {
             name: 'HostCreate',
             path: 'addhost',
-            component: () => import('@/pages/cluster-mange/hosts/create.vue'),
+            component: () => import('@/pages/cluster-manage/hosts/create.vue'),
             meta: {
               hidden: true,
-              activeMenu: '/cluster-mange/hosts'
+              activeMenu: '/cluster-manage/hosts'
             }
           }
         ]
diff --git a/bigtop-manager-ui/src/router/routes/modules/system.ts b/bigtop-manager-ui/src/router/routes/modules/system.ts
index af677deb..0bfaf729 100644
--- a/bigtop-manager-ui/src/router/routes/modules/system.ts
+++ b/bigtop-manager-ui/src/router/routes/modules/system.ts
@@ -22,9 +22,9 @@ import pageView from '@/layouts/index.vue'
 
 const routes: RouteRecordRaw[] = [
   {
-    path: '/system-mange/',
+    path: '/system-manage/',
     component: pageView,
-    redirect: '/system-mange/llm-config',
+    redirect: '/system-manage/llm-config',
     meta: {
       title: 'menu.system'
     },
@@ -32,7 +32,7 @@ const routes: RouteRecordRaw[] = [
       {
         name: 'LlmConfig',
         path: 'llm-config',
-        component: () => import('@/pages/system-mange/llm-config/index.vue'),
+        component: () => import('@/pages/system-manage/llm-config/index.vue'),
         meta: {
           icon: 'llm_config',
           title: 'menu.llm_config'
diff --git a/bigtop-manager-ui/src/store/ai-assistant/index.ts b/bigtop-manager-ui/src/store/ai-assistant/index.ts
new file mode 100644
index 00000000..35fb6b81
--- /dev/null
+++ b/bigtop-manager-ui/src/store/ai-assistant/index.ts
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import dayjs from 'dayjs'
+import * as ai from '@/api/ai-assistant/index'
+import { defineStore, storeToRefs } from 'pinia'
+import { computed, ref, watch } from 'vue'
+import { SenderType } from '@/api/ai-assistant/types'
+import { getRandomFromTimestamp } from '@/utils/tools'
+import { useLlmConfigStore } from '../llm-config'
+import type { AxiosProgressEvent, Canceler } from 'axios'
+import type { ChatMessageItem, ChatThread, ReceivedMessageItem, ThreadId } from '@/api/ai-assistant/types'
+
+export const useAiChatStore = defineStore(
+  'ai-assistant',
+  () => {
+    const llmConfigStore = useLlmConfigStore()
+    const { currAuthPlatform } = storeToRefs(llmConfigStore)
+
+    const currThread = ref<ChatThread>({})
+    const threads = ref<ChatThread[]>([])
+    const chatRecords = ref<ChatMessageItem[]>([])
+    const canceler = ref<Canceler>()
+    const messageReceiver = ref('')
+    const isSending = ref(false)
+    const loadingChatRecords = ref(false)
+
+    const hasActivePlatform = computed(() => Object.keys(currAuthPlatform.value || {}).length > 0)
+    const threadLimit = computed(() => threads.value.length >= 10)
+
+    watch(currAuthPlatform, (val, oldVal) => {
+      if (val == undefined || !hasActivePlatform.value) {
+        currThread.value = {}
+      } else if (val.llmConfigId !== oldVal?.llmConfigId) {
+        currThread.value = {}
+      }
+    })
+
+    const resetState = () => {
+      currThread.value = {}
+      threads.value = []
+      messageReceiver.value = ''
+      isSending.value = false
+      loadingChatRecords.value = false
+    }
+
+    const initCurrThread = () => {
+      if (!hasActivePlatform.value) {
+        return
+      }
+      if (Object.keys(currThread.value).length == 0) {
+        if (threads.value.length === 0) {
+          createChatThread()
+        } else {
+          currThread.value = threads.value[0]
+          getThreadRecords()
+        }
+      } else {
+        getThreadsFromAuthPlatform()
+        getThreadRecords()
+      }
+    }
+
+    const createChatThread = async (quickCreate = false) => {
+      try {
+        const tempName = `thread-${getRandomFromTimestamp()}`
+        const data = await ai.createChatThread({ id: null, name: tempName })
+        if (data) {
+          await getThread(data.threadId as ThreadId, quickCreate)
+          getThreadsFromAuthPlatform()
+        }
+      } catch (error) {
+        console.log('error :>> ', error)
+      }
+    }
+
+    const updateChatThread = async (thread: ChatThread, newName: string) => {
+      try {
+        const threadId = thread.threadId as ThreadId
+        await ai.updateThread(threadId, { name: newName })
+        return true
+      } catch (error) {
+        return false
+      }
+    }
+
+    const deleteChatThread = async (thread: ChatThread) => {
+      try {
+        const threadId = thread.threadId as ThreadId
+        await ai.deleteThread(threadId)
+        return true
+      } catch (error) {
+        return false
+      }
+    }
+
+    const getThread = async (threadId: ThreadId, quickCreate = false) => {
+      try {
+        const data = await ai.getThread(threadId)
+        currThread.value = data
+        !quickCreate && (await getThreadRecords())
+      } catch (error) {
+        console.log('error :>> ', error)
+      }
+    }
+
+    const getThreadsFromAuthPlatform = async () => {
+      try {
+        const data = await ai.getThreadsFromAuthPlatform()
+        threads.value = data
+      } catch (error) {
+        console.log('error :>> ', error)
+      }
+    }
+
+    const getThreadRecords = async () => {
+      try {
+        loadingChatRecords.value = true
+        const { threadId } = currThread.value
+        const data = await ai.getThreadRecords(threadId as ThreadId)
+        chatRecords.value = data
+      } catch (error) {
+        console.log('error :>> ', error)
+      } finally {
+        loadingChatRecords.value = false
+      }
+    }
+
+    const talkWithChatbot = async (message: string) => {
+      try {
+        const { threadId } = currThread.value
+        isSending.value = true
+        if (threadId) {
+          const { cancel, promise } = ai.talkWithChatbot(threadId, { message }, onMessageReceive)
+          canceler.value = cancel
+          return promise.then(onMessageComplete)
+        }
+      } catch (error) {
+        isSending.value = false
+        console.log('error :>> ', error)
+      }
+    }
+
+    const collectReceiveMessage = async (message: string) => {
+      try {
+        if (threads.value.length === 0) {
+          await createChatThread(true)
+        }
+        const res = await talkWithChatbot(message)
+        setChatRecordForSender('AI', res?.message || '')
+      } catch (error) {
+        console.log('error :>> ', error)
+      }
+    }
+
+    const checkReceiveMessageError = (item: ReceivedMessageItem) => {
+      if (item.content != undefined) {
+        return item.content
+      } else if (item.finishReason && item.finishReason != 'completed') {
+        return item.finishReason
+      }
+    }
+
+    const onMessageReceive = ({ event }: AxiosProgressEvent) => {
+      messageReceiver.value = event.target.responseText
+        .split('data:')
+        .map((stream: string) => {
+          if (stream.length > 0) {
+            const parsedMsgInfo = JSON.parse(stream.trimEnd())
+            return checkReceiveMessageError(parsedMsgInfo)
+          }
+        })
+        .join('')
+    }
+
+    const onMessageComplete = () => {
+      const formatResultMsg = messageReceiver.value
+      messageReceiver.value = ''
+      cancelSseConnect()
+      isSending.value = false
+      return Promise.resolve({ message: formatResultMsg, state: true })
+    }
+
+    const cancelSseConnect = () => {
+      if (!canceler.value) {
+        return
+      }
+      canceler.value()
+    }
+
+    const setChatRecordForSender = async (sender: SenderType, message: string) => {
+      chatRecords.value.push({
+        sender,
+        message,
+        createTime: dayjs().format()
+      })
+    }
+
+    return {
+      currThread,
+      threads,
+      chatRecords,
+      messageReceiver,
+      isSending,
+      loadingChatRecords,
+      threadLimit,
+      hasActivePlatform,
+      initCurrThread,
+      talkWithChatbot,
+      createChatThread,
+      updateChatThread,
+      deleteChatThread,
+      getThread,
+      getThreadsFromAuthPlatform,
+      cancelSseConnect,
+      getThreadRecords,
+      setChatRecordForSender,
+      collectReceiveMessage,
+      resetState
+    }
+  },
+  {
+    persist: {
+      storage: sessionStorage,
+      paths: ['currThread']
+    }
+  }
+)
diff --git a/bigtop-manager-ui/src/store/llm-config/config.ts b/bigtop-manager-ui/src/store/llm-config/config.ts
index e8ee24ce..7aa767d5 100644
--- a/bigtop-manager-ui/src/store/llm-config/config.ts
+++ b/bigtop-manager-ui/src/store/llm-config/config.ts
@@ -33,17 +33,13 @@ export function useFormItemConfig() {
         rules: [
           {
             required: true,
-            message: t('common.enter_error', [
-              `${t('llmConfig.name')}`.toLowerCase()
-            ]),
+            message: t('common.enter_error', [`${t('llmConfig.name')}`.toLowerCase()]),
             trigger: 'blur'
           }
         ]
       },
       controlProps: {
-        placeholder: t('common.enter_error', [
-          `${t('llmConfig.name')}`.toLowerCase()
-        ])
+        placeholder: t('common.enter_error', [`${t('llmConfig.name')}`.toLowerCase()])
       }
     },
     {
@@ -60,17 +56,13 @@ export function useFormItemConfig() {
         rules: [
           {
             required: true,
-            message: t('common.select_error', [
-              t('llmConfig.platform_name').toLowerCase()
-            ]),
+            message: t('common.select_error', [t('llmConfig.platform_name').toLowerCase()]),
             trigger: 'blur'
           }
         ]
       },
       controlProps: {
-        placeholder: t('common.select_error', [
-          t('llmConfig.platform_name').toLowerCase()
-        ])
+        placeholder: t('common.select_error', [t('llmConfig.platform_name').toLowerCase()])
       }
     },
     {
@@ -82,17 +74,13 @@ export function useFormItemConfig() {
         rules: [
           {
             required: true,
-            message: t('common.select_error', [
-              t('llmConfig.model').toLowerCase()
-            ]),
+            message: t('common.select_error', [t('llmConfig.model').toLowerCase()]),
             trigger: 'blur'
           }
         ]
       },
       controlProps: {
-        placeholder: t('common.select_error', [
-          t('llmConfig.model').toLowerCase()
-        ])
+        placeholder: t('common.select_error', [t('llmConfig.model').toLowerCase()])
       }
     },
     {
@@ -114,11 +102,7 @@ export function useFormItemConfig() {
       }
     }
   ])
-  const createNewFormItem = (
-    type: string,
-    field: string,
-    label: string
-  ): FormItemState => {
+  const createNewFormItem = (type: string, field: string, label: string): FormItemState => {
     return {
       type,
       field,
diff --git a/bigtop-manager-ui/src/store/llm-config/index.ts b/bigtop-manager-ui/src/store/llm-config/index.ts
index e09936a1..1249badf 100644
--- a/bigtop-manager-ui/src/store/llm-config/index.ts
+++ b/bigtop-manager-ui/src/store/llm-config/index.ts
@@ -18,7 +18,7 @@
  */
 
 import { defineStore } from 'pinia'
-import { computed, ref } from 'vue'
+import { computed, ref, shallowRef, watch } from 'vue'
 import * as llmServer from '@/api/llm-config/index'
 
 import type {
@@ -38,9 +38,14 @@ type Platform = {
   name: string
   supportModels: string[]
 }
+type ActiveAuthPlatform = {
+  llmConfigId: string | number
+  platformName: string
+  model: string
+}
 
 export const useLlmConfigStore = defineStore(
-  'llmConfig',
+  'llm-config',
   () => {
     const loading = ref<boolean>(false)
     const loadingTest = ref<boolean>(false)
@@ -49,13 +54,13 @@ export const useLlmConfigStore = defineStore(
     const formCredentials = ref<PlatformCredential[]>([])
     const authorizedPlatforms = ref<AuthorizedPlatform[]>([])
     const currPlatform = ref<currPlatform>({})
+    const currAuthPlatform = shallowRef<ActiveAuthPlatform>()
 
     const formKeys = computed(() => formCredentials.value.map((v) => v.name))
     const isDisabled = computed(() => loading.value || loadingTest.value)
     const supportModels = computed(() => {
       const { platformId } = currPlatform.value
-      return platforms.value.find((item) => item.id === platformId)
-        ?.supportModels
+      return platforms.value.find((item) => item.id === platformId)?.supportModels
     })
     const authCredentials = computed(() =>
       formCredentials.value.map((v) => ({
@@ -64,6 +69,22 @@ export const useLlmConfigStore = defineStore(
       }))
     )
 
+    watch(
+      () => authorizedPlatforms.value,
+      (val) => {
+        const active = val.filter((v) => v.status === 1)[0] as AuthorizedPlatform
+        if (!active) {
+          currAuthPlatform.value = undefined
+          return
+        }
+        const { platformName, model, id } = active
+        currAuthPlatform.value = { llmConfigId: id, platformName, model }
+      },
+      {
+        deep: true
+      }
+    )
+
     const getAuthorizedPlatformConfig = () => {
       return {
         ...currPlatform.value,
@@ -72,9 +93,9 @@ export const useLlmConfigStore = defineStore(
       } as UpdateAuthorizedPlatformConfig
     }
 
-    const getAuthorizedPlatforms = async () => {
-      loading.value = true
+    const getAuthorizedPlatforms = async (hasLoading = true) => {
       try {
+        hasLoading && (loading.value = true)
         authorizedPlatforms.value = await llmServer.getAuthorizedPlatforms()
       } catch (error) {
         console.log('error :>> ', error)
@@ -96,8 +117,7 @@ export const useLlmConfigStore = defineStore(
     const getPlatformCredentials = async () => {
       try {
         const platformId = currPlatform.value.platformId as number
-        formCredentials.value =
-          await llmServer.getPlatformCredentials(platformId)
+        formCredentials.value = await llmServer.getPlatformCredentials(platformId)
       } catch (error) {
         console.log('error :>> ', error)
       }
@@ -106,7 +126,6 @@ export const useLlmConfigStore = defineStore(
     const getAuthPlatformDetail = async () => {
       try {
         const authId = currPlatform.value.id as number
-        await getPlatformCredentials()
         const data = await llmServer.getAuthPlatformDetail(authId)
         Object.assign(currPlatform.value, data.authCredentials)
       } catch (error) {
@@ -115,9 +134,9 @@ export const useLlmConfigStore = defineStore(
     }
 
     const addAuthorizedPlatform = async () => {
-      loading.value = true
-      const authorizedPlatformConfig = getAuthorizedPlatformConfig()
       try {
+        loading.value = true
+        const authorizedPlatformConfig = getAuthorizedPlatformConfig()
         return await llmServer.addAuthorizedPlatform(authorizedPlatformConfig)
       } catch (error) {
         console.log('error :>> ', error)
@@ -128,9 +147,9 @@ export const useLlmConfigStore = defineStore(
     }
 
     const updateAuthPlatform = async () => {
-      loading.value = true
-      const authorizedPlatformConfig = getAuthorizedPlatformConfig()
       try {
+        loading.value = true
+        const authorizedPlatformConfig = getAuthorizedPlatformConfig()
         return await llmServer.updateAuthPlatform(authorizedPlatformConfig)
       } catch (error) {
         console.log('error :>> ', error)
@@ -141,12 +160,10 @@ export const useLlmConfigStore = defineStore(
     }
 
     const testAuthorizedPlatform = async () => {
-      loadingTest.value = true
-      const authorizedPlatformConfig = getAuthorizedPlatformConfig()
       try {
-        const data = await llmServer.testAuthorizedPlatform(
-          authorizedPlatformConfig
-        )
+        loadingTest.value = true
+        const authorizedPlatformConfig = getAuthorizedPlatformConfig()
+        const data = await llmServer.testAuthorizedPlatform(authorizedPlatformConfig)
         testPassed.value = data
         return data
       } catch (error) {
@@ -186,6 +203,7 @@ export const useLlmConfigStore = defineStore(
       supportModels,
       platforms,
       isDisabled,
+      currAuthPlatform,
       getPlatforms,
       getAuthPlatformDetail,
       getAuthorizedPlatforms,
@@ -200,6 +218,9 @@ export const useLlmConfigStore = defineStore(
     }
   },
   {
-    persist: false
+    persist: {
+      storage: sessionStorage,
+      paths: ['currAuthPlatform']
+    }
   }
 )
diff --git a/bigtop-manager-ui/src/store/menu/helper.ts b/bigtop-manager-ui/src/store/menu/helper.ts
index 489da4dc..69810f3b 100644
--- a/bigtop-manager-ui/src/store/menu/helper.ts
+++ b/bigtop-manager-ui/src/store/menu/helper.ts
@@ -20,7 +20,5 @@
 import { MenuItem } from './types'
 
 export const findActivePath = (menu: MenuItem): string | undefined => {
-  return menu?.children && menu?.children.length > 0
-    ? findActivePath(menu.children[0])
-    : menu?.key
+  return menu?.children && menu?.children.length > 0 ? findActivePath(menu.children[0]) : menu?.key
 }
diff --git a/bigtop-manager-ui/src/store/menu/index.ts b/bigtop-manager-ui/src/store/menu/index.ts
index fd145788..887bd24d 100644
--- a/bigtop-manager-ui/src/store/menu/index.ts
+++ b/bigtop-manager-ui/src/store/menu/index.ts
@@ -40,16 +40,13 @@ export const useMenuStore = defineStore(
     const headerSelectedKey = ref(headerMenus.value[0].path)
 
     const hasCluster = computed(() => clusters.value.length > 0)
-    const isClusterCreateVisible = computed(() =>
-      RouteExceptions.SPECIAL_ROUTE_PATH.includes(route.matched[0].path)
-    )
+    const isClusterCreateVisible = computed(() => RouteExceptions.SPECIAL_ROUTE_PATH.includes(route.matched[0].path))
     const isDynamicRouteMatched = computed(() => {
       const path = route.matched.at(-1)?.path
       return path?.includes(RouteExceptions.DYNAMIC_ROUTE_MATCH)
     })
     const siderMenus = computed((): MenuItem[] => {
-      const siderMenuTemplate =
-        baseRoutesMap.value.get(headerSelectedKey.value) || []
+      const siderMenuTemplate = baseRoutesMap.value.get(headerSelectedKey.value) || []
       const formatSider = cloneDeep(siderMenuTemplate)
       if (hasCluster.value) {
         updateSiderItemByClusters(formatSider as MenuItem[])
@@ -67,10 +64,7 @@ export const useMenuStore = defineStore(
     watchEffect(() => {
       // resolve highlight menu
       const activeMenu = route.meta.activeMenu || route.path
-      const matchedNames = [
-        RouteExceptions.SPECIAL_ROUTE_NAME,
-        RouteExceptions.DEFAULT_ROUTE_NAME
-      ] as string[]
+      const matchedNames = [RouteExceptions.SPECIAL_ROUTE_NAME, RouteExceptions.DEFAULT_ROUTE_NAME] as string[]
       headerSelectedKey.value = route.matched[0].path
 
       if (matchedNames.includes(route.name as string)) {
@@ -116,10 +110,7 @@ export const useMenuStore = defineStore(
             label: meta.title || '',
             title: meta.title || '',
             name: name || '',
-            children:
-              children && children.length > 0
-                ? formatRouteToMenu(children, fullPath)
-                : []
+            children: children && children.length > 0 ? formatRouteToMenu(children, fullPath) : []
           }
         })
     }
@@ -128,20 +119,14 @@ export const useMenuStore = defineStore(
       dr.forEach((route) => {
         if (!route.meta?.hidden) {
           const exist = baseRoutesMap.value.get(route.path) || []
-          baseRoutesMap.value.set(route.path, [
-            ...exist,
-            ...formatRouteToMenu(route.children || [], route.path)
-          ])
+          baseRoutesMap.value.set(route.path, [...exist, ...formatRouteToMenu(route.children || [], route.path)])
         }
       })
     }
 
     const onHeaderClick = (key: string) => {
       headerSelectedKey.value = key
-      if (
-        !hasCluster.value &&
-        route.name == RouteExceptions.SPECIAL_ROUTE_NAME
-      ) {
+      if (!hasCluster.value && route.name == RouteExceptions.SPECIAL_ROUTE_NAME) {
         return
       }
       siderMenuSelectedKey.value = findActivePath(siderMenus.value[0])
@@ -154,12 +139,8 @@ export const useMenuStore = defineStore(
     }
 
     const updateSiderMenu = async (isDelete = false) => {
-      isDelete
-        ? await clusterStore.delCluster()
-        : await clusterStore.addCluster()
-      siderMenuSelectedKey.value =
-        siderMenus.value[0].children?.at(-1)?.key ||
-        RouteExceptions.SPECIAL_ROUTE_PATH
+      isDelete ? await clusterStore.delCluster() : await clusterStore.addCluster()
+      siderMenuSelectedKey.value = siderMenus.value[0].children?.at(-1)?.key || RouteExceptions.SPECIAL_ROUTE_PATH
       router.push(siderMenuSelectedKey.value)
     }
 
diff --git a/bigtop-manager-ui/src/styles/ant-design-vue.scss b/bigtop-manager-ui/src/styles/ant-design-vue.scss
index 520b1543..a23e7d2f 100644
--- a/bigtop-manager-ui/src/styles/ant-design-vue.scss
+++ b/bigtop-manager-ui/src/styles/ant-design-vue.scss
@@ -380,4 +380,4 @@ $box-shadow-tabs-overflow-right: var(--box-shadow-tabs-overflow-right);
 $box-shadow-tabs-overflow-top: var(--box-shadow-tabs-overflow-top);
 $box-shadow-tabs-overflow-bottom: var(--box-shadow-tabs-overflow-bottom);
 $_token-key: var(--_token-key);
-$_hash-id: var(--_hash-id);
\ No newline at end of file
+$_hash-id: var(--_hash-id);
diff --git a/bigtop-manager-ui/src/styles/index.scss b/bigtop-manager-ui/src/styles/index.scss
index 3744349c..7e114fdf 100644
--- a/bigtop-manager-ui/src/styles/index.scss
+++ b/bigtop-manager-ui/src/styles/index.scss
@@ -18,8 +18,6 @@
  */
 
 @import './variables.scss';
-@import './marked.scss';
-@import './scrollbar.scss';
 
 #app {
   width: 100%;
@@ -46,4 +44,4 @@
     width: 20px;
     height: 20px;
   }
-}
\ No newline at end of file
+}
diff --git a/bigtop-manager-ui/src/styles/marked.scss b/bigtop-manager-ui/src/styles/marked.scss
index 1d6b6cbf..a035bc6e 100644
--- a/bigtop-manager-ui/src/styles/marked.scss
+++ b/bigtop-manager-ui/src/styles/marked.scss
@@ -21,6 +21,7 @@
 @import 'highlight.js/styles/github-dark.css';
 
 .markdown-body {
+  display: grid;
   background-color: inherit;
   color: #333333;
   box-sizing: border-box;
@@ -61,4 +62,4 @@
   justify-content: space-between;
   padding: 6px;
   align-items: center;
-}
\ No newline at end of file
+}
diff --git a/bigtop-manager-ui/src/styles/scrollbar.scss b/bigtop-manager-ui/src/styles/scrollbar.scss
index 4e38af8f..7aa32f96 100644
--- a/bigtop-manager-ui/src/styles/scrollbar.scss
+++ b/bigtop-manager-ui/src/styles/scrollbar.scss
@@ -48,4 +48,4 @@
   border-radius: 10px;
   background: #c1c1c1;
   box-shadow: inset 0 0 1px rgba(0, 102, 102, 0.144);
-}
\ No newline at end of file
+}
diff --git a/bigtop-manager-ui/src/styles/variables.scss b/bigtop-manager-ui/src/styles/variables.scss
index 30dbe010..ee3ab848 100644
--- a/bigtop-manager-ui/src/styles/variables.scss
+++ b/bigtop-manager-ui/src/styles/variables.scss
@@ -27,17 +27,17 @@ $space-sm: 8px;
 $border-radius-sm: 8px;
 
 // border
-$color-border: #E5E5E5;
+$color-border: #e5e5e5;
 
 $layout-header-height: 64px;
 $layout-sider-width: 200px;
 $layout-sider-bg-color: $color-bg-base;
 
-@mixin flexbox($direction: null, $wrap: null, $justify: null, $align: null, $gap:null) {
+@mixin flexbox($direction: null, $wrap: null, $justify: null, $align: null, $gap: null) {
   display: flex;
   flex-direction: $direction;
   flex-wrap: $wrap;
   justify-content: $justify;
   align-items: $align;
   gap: $gap;
-}
\ No newline at end of file
+}
diff --git a/bigtop-manager-ui/src/utils/array.ts b/bigtop-manager-ui/src/utils/array.ts
index 272ace3e..5f25684e 100644
--- a/bigtop-manager-ui/src/utils/array.ts
+++ b/bigtop-manager-ui/src/utils/array.ts
@@ -48,10 +48,7 @@ const replacePatternInHosts = (rawHostNames: string[]): string[] => {
 
       for (let i = parseInt(start, 10); i < parseInt(end, 10) + 1; i++) {
         hostNames.push(
-          rawHostName.replace(
-            /\[\d*\-\d*\]/,
-            extra[0].substring(0, start.length - i.toString().length) + i
-          )
+          rawHostName.replace(/\[\d*\-\d*\]/, extra[0].substring(0, start.length - i.toString().length) + i)
         )
       }
     } else {
@@ -64,9 +61,7 @@ const replacePatternInHosts = (rawHostNames: string[]): string[] => {
 }
 
 // parse hostNames as pattern expression
-export const parseHostNamesAsPatternExpression = (
-  hostNameStr: string
-): string[] => {
+export const parseHostNamesAsPatternExpression = (hostNameStr: string): string[] => {
   let hostNames: string[] = []
   const hostNameArr = hostNameStr.replace(/\n|\r\n|\s/g, ',').split(',')
   hostNameArr.forEach((a) => {
diff --git a/bigtop-manager-ui/src/utils/router-util.ts b/bigtop-manager-ui/src/utils/router-util.ts
index 04b53e6b..150cfcd1 100644
--- a/bigtop-manager-ui/src/utils/router-util.ts
+++ b/bigtop-manager-ui/src/utils/router-util.ts
@@ -22,9 +22,7 @@ interface RouteModuleType {
   routes: RouteRecordRaw[]
 }
 
-export const mergeRouteModules = (
-  routeModules: Record<string, unknown>
-): RouteRecordRaw[] => {
+export const mergeRouteModules = (routeModules: Record<string, unknown>): RouteRecordRaw[] => {
   const mergedRoutes: RouteRecordRaw[] = []
   for (const routeModule of Object.values(routeModules)) {
     const moduleRoutes = (routeModule as RouteModuleType)?.routes ?? []
diff --git a/bigtop-manager-ui/src/utils/tools.ts b/bigtop-manager-ui/src/utils/tools.ts
index 44597c42..3be3f4d3 100644
--- a/bigtop-manager-ui/src/utils/tools.ts
+++ b/bigtop-manager-ui/src/utils/tools.ts
@@ -17,6 +17,13 @@
  * under the License.
  */
 
+import dayjs from 'dayjs'
+import utc from 'dayjs/plugin/utc'
+import timezone from 'dayjs/plugin/timezone'
+
+dayjs.extend(utc)
+dayjs.extend(timezone)
+
 export const copyText = (text: string): Promise<any> => {
   if (navigator.clipboard) {
     return navigator.clipboard.writeText(text)
@@ -60,3 +67,11 @@ export const scrollToBottom = (container: HTMLElement | null) => {
     container.scrollTop = container.scrollHeight
   })
 }
+
+export const getRandomFromTimestamp = (len: number = 6) => {
+  return Date.now().toString().slice(-len)
+}
+
+export const formatTime = (time: string | undefined, formatRule: string = 'YYYY-MM-DD HH:mm:ss') => {
+  return typeof time === 'string' && dayjs(time).tz(dayjs.tz.guess(), true).format(formatRule)
+}
diff --git a/bigtop-manager-ui/tsconfig.json b/bigtop-manager-ui/tsconfig.json
index f185f274..741ccfd4 100644
--- a/bigtop-manager-ui/tsconfig.json
+++ b/bigtop-manager-ui/tsconfig.json
@@ -3,24 +3,13 @@
     "target": "ES2020",
     "useDefineForClassFields": true,
     "module": "ESNext",
-    "lib": [
-      "ES2020",
-      "DOM",
-      "DOM.Iterable"
-    ],
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
     "baseUrl": "./",
     "paths": {
-      "@/*": [
-        "src/*"
-      ]
+      "@/*": ["src/*"]
     },
     "skipLibCheck": true,
-    "types": [
-      "vitest",
-      "vitest/globals",
-      "vite/client",
-      "vite-plugin-svg-icons/client"
-    ],
+    "types": ["vitest", "vitest/globals", "vite/client", "vite-plugin-svg-icons/client"],
     /* Bundler mode */
     //     "moduleResolution": "Bundler",
     "moduleResolution": "Node",
@@ -36,16 +25,10 @@
     "noUnusedParameters": true,
     "noFallthroughCasesInSwitch": true
   },
-  "include": [
-    "src/**/*.ts",
-    "src/**/*.d.ts",
-    "src/**/*.tsx",
-    "src/**/*.vue",
-    "src/types/**/*.d.ts"
-  ],
+  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/types/**/*.d.ts"],
   "references": [
     {
       "path": "./tsconfig.node.json"
     }
   ]
-}
\ No newline at end of file
+}