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 143bfaf BIGTOP-4234: Add chatbot on UI (#77)
143bfaf is described below
commit 143bfaf91e50fd2e6d32ee78f7df56cf201677ce
Author: Fdefined <[email protected]>
AuthorDate: Fri Sep 27 16:37:54 2024 +0800
BIGTOP-4234: Add chatbot on UI (#77)
---
bigtop-manager-ui/.env.development | 1 -
bigtop-manager-ui/package.json | 5 +
bigtop-manager-ui/pnpm-lock.yaml | 52 ++++
bigtop-manager-ui/src/api/chatbot/index.ts | 108 +++++++++
bigtop-manager-ui/src/api/chatbot/types.ts | 94 ++++++++
bigtop-manager-ui/src/api/sse/index.ts | 24 +-
bigtop-manager-ui/src/api/sse/types.ts | 4 +
.../content.vue => assets/images/svg/close.svg} | 45 +---
.../images/svg/full-screen.svg} | 47 +---
.../content.vue => assets/images/svg/history.svg} | 49 +---
.../content.vue => assets/images/svg/home.svg} | 47 +---
.../content.vue => assets/images/svg/left.svg} | 47 +---
bigtop-manager-ui/src/assets/images/svg/robot.svg | 31 +++
.../images/svg/send-disabled.svg} | 45 +---
.../content.vue => assets/images/svg/send.svg} | 45 +---
.../content.vue => assets/images/svg/user.svg} | 47 +---
.../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 ++++++++++++
bigtop-manager-ui/src/components/common/index.ts | 4 +-
.../src/components/common/markdown-view/index.vue | 110 +++++++++
bigtop-manager-ui/src/composables/use-chat-bot.ts | 241 +++++++++++++++++++
bigtop-manager-ui/src/layouts/content.vue | 2 +
.../src/locales/en_US/{index.ts => ai.ts} | 27 +--
bigtop-manager-ui/src/locales/en_US/common.ts | 9 +-
bigtop-manager-ui/src/locales/en_US/index.ts | 4 +-
.../src/locales/{en_US/index.ts => zh_CN/ai.ts} | 26 +-
bigtop-manager-ui/src/locales/zh_CN/common.ts | 8 +-
bigtop-manager-ui/src/locales/zh_CN/index.ts | 4 +-
bigtop-manager-ui/src/main.ts | 4 +-
bigtop-manager-ui/src/plugins/index.ts | 3 +-
.../styles/{function.scss => common/index.scss} | 5 +-
.../src/styles/{ => common}/mixins.scss | 0
.../src/styles/{ => common}/variables.scss | 0
.../src/styles/{default.css => main.css} | 2 -
bigtop-manager-ui/src/styles/main.scss | 22 --
.../src/styles/{default.css => marked.scss} | 58 +++--
.../src/styles/{scrollbar.css => scrollbar.scss} | 13 +-
.../{locales/en_US/index.ts => utils/render.ts} | 29 ++-
bigtop-manager-ui/vite.config.ts | 2 +-
46 files changed, 2183 insertions(+), 431 deletions(-)
diff --git a/bigtop-manager-ui/.env.development
b/bigtop-manager-ui/.env.development
index 6596210..66fd985 100644
--- a/bigtop-manager-ui/.env.development
+++ b/bigtop-manager-ui/.env.development
@@ -16,6 +16,5 @@
NODE_ENV=development
VITE_APP_BASE='/'
-#VITE_APP_BASE_URL='http://172.29.40.96:8080'
VITE_APP_BASE_URL='http://localhost:8080'
VITE_APP_BASE_API='/api'
diff --git a/bigtop-manager-ui/package.json b/bigtop-manager-ui/package.json
index 71035f4..2c7a53d 100644
--- a/bigtop-manager-ui/package.json
+++ b/bigtop-manager-ui/package.json
@@ -18,11 +18,16 @@
"clipboard": "^2.0.11",
"dayjs": "^1.11.9",
"echarts": "^5.4.3",
+ "github-markdown-css": "^5.6.1",
+ "highlight.js": "^11.10.0",
"lodash": "^4.17.21",
+ "marked": "^14.1.2",
+ "marked-highlight": "^2.1.4",
"md5": "^2.3.0",
"pinia": "^2.1.6",
"pinia-plugin-persistedstate": "^3.2.0",
"vue": "^3.4.37",
+ "vue-dompurify-html": "^5.1.0",
"vue-i18n": "^9.2.2",
"vue-router": "^4.2.4"
},
diff --git a/bigtop-manager-ui/pnpm-lock.yaml b/bigtop-manager-ui/pnpm-lock.yaml
index 33389b3..ebdb434 100644
--- a/bigtop-manager-ui/pnpm-lock.yaml
+++ b/bigtop-manager-ui/pnpm-lock.yaml
@@ -26,9 +26,21 @@ dependencies:
echarts:
specifier: ^5.4.3
version: 5.4.3
+ github-markdown-css:
+ specifier: ^5.6.1
+ version: 5.6.1
+ highlight.js:
+ specifier: ^11.10.0
+ version: 11.10.0
lodash:
specifier: ^4.17.21
version: 4.17.21
+ marked:
+ specifier: ^14.1.2
+ version: 14.1.2
+ marked-highlight:
+ specifier: ^2.1.4
+ version: 2.1.4([email protected])
md5:
specifier: ^2.3.0
version: 2.3.0
@@ -41,6 +53,9 @@ dependencies:
vue:
specifier: ^3.4.37
version: 3.4.38([email protected])
+ vue-dompurify-html:
+ specifier: ^5.1.0
+ version: 5.1.0([email protected])
vue-i18n:
specifier: ^9.2.2
version: 9.2.2([email protected])
@@ -2361,6 +2376,10 @@ packages:
domelementtype: 2.3.0
dev: true
+ /[email protected]:
+ resolution: {integrity:
sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==}
+ dev: false
+
/[email protected]:
resolution: {integrity:
sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==}
dependencies:
@@ -3063,6 +3082,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /[email protected]:
+ resolution: {integrity:
sha512-DItLFgHd+s7HQmk63YN4/TdvLeRqk1QP7pPKTTPrDTYoI5x7f/luJWSOZxesmuxBI2srHp8RDyoZd+9WF+WK8Q==}
+ engines: {node: '>=10'}
+ dev: false
+
/[email protected]:
resolution: {integrity:
sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -3253,6 +3277,11 @@ packages:
hasBin: true
dev: true
+ /[email protected]:
+ resolution: {integrity:
sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==}
+ engines: {node: '>=12.0.0'}
+ dev: false
+
/[email protected]:
resolution: {integrity:
sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
dependencies:
@@ -3824,6 +3853,20 @@ packages:
object-visit: 1.0.1
dev: true
+ /[email protected]([email protected]):
+ resolution: {integrity:
sha512-D1GOkcdzP+1dzjoColL7umojefFrASDuLeyaHS0Zr/Uo9jkr1V6vpLRCzfi1djmEaWyK0SYMFtHnpkZ+cwFT1w==}
+ peerDependencies:
+ marked: '>=4 <15'
+ dependencies:
+ marked: 14.1.2
+ dev: false
+
+ /[email protected]:
+ resolution: {integrity:
sha512-f3r0yqpz31VXiDB/wj9GaOB0a2PRLQl6vJmXiFrniNwjkKdvakqJRULhjFKJpxOchlCRiG5fcacoUZY5Xa6PEQ==}
+ engines: {node: '>= 18'}
+ hasBin: true
+ dev: false
+
/[email protected]:
resolution: {integrity:
sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
dependencies:
@@ -5741,6 +5784,15 @@ packages:
vue: 3.4.38([email protected])
dev: false
+ /[email protected]([email protected]):
+ resolution: {integrity:
sha512-616o2/PBdOLM2bwlRWLdzeEC9NerLkwiudqNgaIJ5vBQWXec+u7Kuzh+45DtQQrids67s4pHnTnJZLVfyPMxbA==}
+ peerDependencies:
+ vue: ^3.0.0
+ dependencies:
+ dompurify: 3.1.6
+ vue: 3.4.38([email protected])
+ dev: false
+
/[email protected]([email protected]):
resolution: {integrity:
sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==}
engines: {node: ^14.17.0 || >=16.0.0}
diff --git a/bigtop-manager-ui/src/api/chatbot/index.ts
b/bigtop-manager-ui/src/api/chatbot/index.ts
new file mode 100644
index 0000000..0b09857
--- /dev/null
+++ b/bigtop-manager-ui/src/api/chatbot/index.ts
@@ -0,0 +1,108 @@
+/*
+ * 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
new file mode 100644
index 0000000..37bc725
--- /dev/null
+++ b/bigtop-manager-ui/src/api/chatbot/types.ts
@@ -0,0 +1,94 @@
+/*
+ * 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/sse/index.ts
b/bigtop-manager-ui/src/api/sse/index.ts
index 22975e5..abcd72d 100644
--- a/bigtop-manager-ui/src/api/sse/index.ts
+++ b/bigtop-manager-ui/src/api/sse/index.ts
@@ -19,7 +19,8 @@
import axios, { type AxiosProgressEvent, type CancelTokenSource } from 'axios'
import request from '@/api/request.ts'
-import type { LogsRes } from './types'
+import type { chatMessagesRes, LogsRes } from './types'
+import type { SendChatMessageCondition } from '@/api/chatbot/types'
export const getLogs = (
clusterId: number,
@@ -40,3 +41,24 @@ export const getLogs = (
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)
+ })
+
+ 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 6f83d3e..7bb9a34 100644
--- a/bigtop-manager-ui/src/api/sse/types.ts
+++ b/bigtop-manager-ui/src/api/sse/types.ts
@@ -21,3 +21,7 @@ export interface LogsRes {
promise: Promise<any>
cancel: () => void
}
+export interface chatMessagesRes {
+ promise: Promise<any>
+ cancel: () => void
+}
diff --git a/bigtop-manager-ui/src/layouts/content.vue
b/bigtop-manager-ui/src/assets/images/svg/close.svg
similarity index 51%
copy from bigtop-manager-ui/src/layouts/content.vue
copy to bigtop-manager-ui/src/assets/images/svg/close.svg
index 1bde145..ec82ea9 100644
--- a/bigtop-manager-ui/src/layouts/content.vue
+++ b/bigtop-manager-ui/src/assets/images/svg/close.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,43 +17,7 @@
~ specific language governing permissions and limitations
~ under the License.
-->
-
-<script setup lang="ts">
- import { HomeOutlined } from '@ant-design/icons-vue'
-</script>
-
-<template>
- <a-layout-content class="container">
- <a-breadcrumb class="breadcrumb">
- <a-breadcrumb-item>
- <home-outlined />
- </a-breadcrumb-item>
- <a-breadcrumb-item
- v-for="(item, index) in $route.path.substring(1).split('/')"
- :key="index"
- >
- {{ item }}
- </a-breadcrumb-item>
- </a-breadcrumb>
- <div class="content">
- <router-view />
- </div>
- </a-layout-content>
-</template>
-
-<style scoped lang="scss">
- .container {
- margin: 0 1rem;
- min-height: auto;
-
- .breadcrumb {
- margin: 1rem 0;
- }
-
- .content {
- padding: 1rem;
- border-radius: 0.5rem;
- background: #fff;
- }
- }
-</style>
+<svg width="20" height="20" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
+ <path d="M8 8L40 40" stroke="#333" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round" />
+ <path d="M8 40L40 8" stroke="#333" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round" />
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/layouts/content.vue
b/bigtop-manager-ui/src/assets/images/svg/full-screen.svg
similarity index 51%
copy from bigtop-manager-ui/src/layouts/content.vue
copy to bigtop-manager-ui/src/assets/images/svg/full-screen.svg
index 1bde145..a6b45d0 100644
--- a/bigtop-manager-ui/src/layouts/content.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,43 +17,9 @@
~ specific language governing permissions and limitations
~ under the License.
-->
-
-<script setup lang="ts">
- import { HomeOutlined } from '@ant-design/icons-vue'
-</script>
-
-<template>
- <a-layout-content class="container">
- <a-breadcrumb class="breadcrumb">
- <a-breadcrumb-item>
- <home-outlined />
- </a-breadcrumb-item>
- <a-breadcrumb-item
- v-for="(item, index) in $route.path.substring(1).split('/')"
- :key="index"
- >
- {{ item }}
- </a-breadcrumb-item>
- </a-breadcrumb>
- <div class="content">
- <router-view />
- </div>
- </a-layout-content>
-</template>
-
-<style scoped lang="scss">
- .container {
- margin: 0 1rem;
- min-height: auto;
-
- .breadcrumb {
- margin: 1rem 0;
- }
-
- .content {
- padding: 1rem;
- border-radius: 0.5rem;
- background: #fff;
- }
- }
-</style>
+<svg width="20" height="20" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
+ <path d="M33 6H42V15" stroke="#333" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round" />
+ <path d="M42 33V42H33" stroke="#333" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round" />
+ <path d="M15 42H6V33" stroke="#333" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round" />
+ <path d="M6 15V6H15" stroke="#333" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round" />
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/layouts/content.vue
b/bigtop-manager-ui/src/assets/images/svg/history.svg
similarity index 51%
copy from bigtop-manager-ui/src/layouts/content.vue
copy to bigtop-manager-ui/src/assets/images/svg/history.svg
index 1bde145..4c40a45 100644
--- a/bigtop-manager-ui/src/layouts/content.vue
+++ b/bigtop-manager-ui/src/assets/images/svg/history.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,43 +17,11 @@
~ specific language governing permissions and limitations
~ under the License.
-->
-
-<script setup lang="ts">
- import { HomeOutlined } from '@ant-design/icons-vue'
-</script>
-
-<template>
- <a-layout-content class="container">
- <a-breadcrumb class="breadcrumb">
- <a-breadcrumb-item>
- <home-outlined />
- </a-breadcrumb-item>
- <a-breadcrumb-item
- v-for="(item, index) in $route.path.substring(1).split('/')"
- :key="index"
- >
- {{ item }}
- </a-breadcrumb-item>
- </a-breadcrumb>
- <div class="content">
- <router-view />
- </div>
- </a-layout-content>
-</template>
-
-<style scoped lang="scss">
- .container {
- margin: 0 1rem;
- min-height: auto;
-
- .breadcrumb {
- margin: 1rem 0;
- }
-
- .content {
- padding: 1rem;
- border-radius: 0.5rem;
- background: #fff;
- }
- }
-</style>
+<svg width="20" height="20" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
+ <path d="M5.81836 6.72729V14H13.0911" stroke="#333" stroke-width="4"
stroke-linecap="round" stroke-linejoin="round" />
+ <path
+ d="M4 24C4 35.0457 12.9543 44 24 44V44C35.0457 44 44 35.0457 44 24C44
12.9543 35.0457 4 24 4C16.598 4 10.1351 8.02111 6.67677 13.9981"
+ stroke="#333" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round" />
+ <path d="M24.005 12L24.0038 24.0088L32.4832 32.4882" stroke="#333"
stroke-width="4" stroke-linecap="round"
+ stroke-linejoin="round" />
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/layouts/content.vue
b/bigtop-manager-ui/src/assets/images/svg/home.svg
similarity index 51%
copy from bigtop-manager-ui/src/layouts/content.vue
copy to bigtop-manager-ui/src/assets/images/svg/home.svg
index 1bde145..de12667 100644
--- a/bigtop-manager-ui/src/layouts/content.vue
+++ b/bigtop-manager-ui/src/assets/images/svg/home.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,43 +17,9 @@
~ specific language governing permissions and limitations
~ under the License.
-->
-
-<script setup lang="ts">
- import { HomeOutlined } from '@ant-design/icons-vue'
-</script>
-
-<template>
- <a-layout-content class="container">
- <a-breadcrumb class="breadcrumb">
- <a-breadcrumb-item>
- <home-outlined />
- </a-breadcrumb-item>
- <a-breadcrumb-item
- v-for="(item, index) in $route.path.substring(1).split('/')"
- :key="index"
- >
- {{ item }}
- </a-breadcrumb-item>
- </a-breadcrumb>
- <div class="content">
- <router-view />
- </div>
- </a-layout-content>
-</template>
-
-<style scoped lang="scss">
- .container {
- margin: 0 1rem;
- min-height: auto;
-
- .breadcrumb {
- margin: 1rem 0;
- }
-
- .content {
- padding: 1rem;
- border-radius: 0.5rem;
- background: #fff;
- }
- }
-</style>
+<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
+ <path d="M9 18V42H39V18L24 6L9 18Z" fill="none" stroke="#333"
stroke-width="4" stroke-linecap="round"
+ stroke-linejoin="round" />
+ <path d="M19 29V42H29V29H19Z" fill="none" stroke="#333" stroke-width="4"
stroke-linejoin="round" />
+ <path d="M9 42H39" stroke="#333" stroke-width="4" stroke-linecap="round" />
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/layouts/content.vue
b/bigtop-manager-ui/src/assets/images/svg/left.svg
similarity index 51%
copy from bigtop-manager-ui/src/layouts/content.vue
copy to bigtop-manager-ui/src/assets/images/svg/left.svg
index 1bde145..01ee633 100644
--- a/bigtop-manager-ui/src/layouts/content.vue
+++ b/bigtop-manager-ui/src/assets/images/svg/left.svg
@@ -1,4 +1,5 @@
-<!--
+<?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
@@ -16,43 +17,7 @@
~ specific language governing permissions and limitations
~ under the License.
-->
-
-<script setup lang="ts">
- import { HomeOutlined } from '@ant-design/icons-vue'
-</script>
-
-<template>
- <a-layout-content class="container">
- <a-breadcrumb class="breadcrumb">
- <a-breadcrumb-item>
- <home-outlined />
- </a-breadcrumb-item>
- <a-breadcrumb-item
- v-for="(item, index) in $route.path.substring(1).split('/')"
- :key="index"
- >
- {{ item }}
- </a-breadcrumb-item>
- </a-breadcrumb>
- <div class="content">
- <router-view />
- </div>
- </a-layout-content>
-</template>
-
-<style scoped lang="scss">
- .container {
- margin: 0 1rem;
- min-height: auto;
-
- .breadcrumb {
- margin: 1rem 0;
- }
-
- .content {
- padding: 1rem;
- border-radius: 0.5rem;
- background: #fff;
- }
- }
-</style>
+<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
+ xmlns="http://www.w3.org/2000/svg">
+ <path d="M31 36L19 24L31 12" stroke="#333" stroke-width="4"
stroke-linecap="round" stroke-linejoin="round" />
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/assets/images/svg/robot.svg
b/bigtop-manager-ui/src/assets/images/svg/robot.svg
new file mode 100644
index 0000000..bb7b0e9
--- /dev/null
+++ b/bigtop-manager-ui/src/assets/images/svg/robot.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 width="48" height="48" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
+ <rect x="9" y="18" width="30" height="24" rx="2" fill="none" stroke="#333"
stroke-width="2" />
+ <circle cx="17" cy="26" r="2" fill="#333" />
+ <circle cx="31" cy="26" r="2" fill="#333" />
+ <path
+ d="M20 32C18.8954 32 18 32.8954 18 34C18 35.1046 18.8954 36 20 36V32ZM28
36C29.1046 36 30 35.1046 30 34C30 32.8954 29.1046 32 28 32V36ZM20
36H28V32H20V36Z"
+ fill="#333" />
+ <path d="M24 10V18" stroke="#333" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round" />
+ <path d="M4 26V34" stroke="#333" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round" />
+ <path d="M44 26V34" stroke="#333" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round" />
+ <circle cx="24" cy="8" r="2" stroke="#333" stroke-width="4" />
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/layouts/content.vue
b/bigtop-manager-ui/src/assets/images/svg/send-disabled.svg
similarity index 51%
copy from bigtop-manager-ui/src/layouts/content.vue
copy to bigtop-manager-ui/src/assets/images/svg/send-disabled.svg
index 1bde145..51b2a88 100644
--- a/bigtop-manager-ui/src/layouts/content.vue
+++ b/bigtop-manager-ui/src/assets/images/svg/send-disabled.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,43 +17,7 @@
~ specific language governing permissions and limitations
~ under the License.
-->
-
-<script setup lang="ts">
- import { HomeOutlined } from '@ant-design/icons-vue'
-</script>
-
-<template>
- <a-layout-content class="container">
- <a-breadcrumb class="breadcrumb">
- <a-breadcrumb-item>
- <home-outlined />
- </a-breadcrumb-item>
- <a-breadcrumb-item
- v-for="(item, index) in $route.path.substring(1).split('/')"
- :key="index"
- >
- {{ item }}
- </a-breadcrumb-item>
- </a-breadcrumb>
- <div class="content">
- <router-view />
- </div>
- </a-layout-content>
-</template>
-
-<style scoped lang="scss">
- .container {
- margin: 0 1rem;
- min-height: auto;
-
- .breadcrumb {
- margin: 1rem 0;
- }
-
- .content {
- padding: 1rem;
- border-radius: 0.5rem;
- background: #fff;
- }
- }
-</style>
+<svg width="20" height="20" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
+ <path d="M43 5L29.7 43L22.1 25.9L5 18.3L43 5Z" stroke="#a5a2a2"
stroke-width="4" stroke-linejoin="round" />
+ <path d="M43.0001 5L22.1001 25.9" stroke="#a5a2a2" stroke-width="4"
stroke-linecap="round" stroke-linejoin="round" />
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/layouts/content.vue
b/bigtop-manager-ui/src/assets/images/svg/send.svg
similarity index 51%
copy from bigtop-manager-ui/src/layouts/content.vue
copy to bigtop-manager-ui/src/assets/images/svg/send.svg
index 1bde145..62ce314 100644
--- a/bigtop-manager-ui/src/layouts/content.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,43 +17,7 @@
~ specific language governing permissions and limitations
~ under the License.
-->
-
-<script setup lang="ts">
- import { HomeOutlined } from '@ant-design/icons-vue'
-</script>
-
-<template>
- <a-layout-content class="container">
- <a-breadcrumb class="breadcrumb">
- <a-breadcrumb-item>
- <home-outlined />
- </a-breadcrumb-item>
- <a-breadcrumb-item
- v-for="(item, index) in $route.path.substring(1).split('/')"
- :key="index"
- >
- {{ item }}
- </a-breadcrumb-item>
- </a-breadcrumb>
- <div class="content">
- <router-view />
- </div>
- </a-layout-content>
-</template>
-
-<style scoped lang="scss">
- .container {
- margin: 0 1rem;
- min-height: auto;
-
- .breadcrumb {
- margin: 1rem 0;
- }
-
- .content {
- padding: 1rem;
- border-radius: 0.5rem;
- background: #fff;
- }
- }
-</style>
+<svg width="20" height="20" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
+ <path d="M43 5L29.7 43L22.1 25.9L5 18.3L43 5Z" stroke="#fff"
stroke-width="4" stroke-linejoin="round" />
+ <path d="M43.0001 5L22.1001 25.9" stroke="#fff" stroke-width="4"
stroke-linecap="round" stroke-linejoin="round" />
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/layouts/content.vue
b/bigtop-manager-ui/src/assets/images/svg/user.svg
similarity index 51%
copy from bigtop-manager-ui/src/layouts/content.vue
copy to bigtop-manager-ui/src/assets/images/svg/user.svg
index 1bde145..8bb9e8f 100644
--- a/bigtop-manager-ui/src/layouts/content.vue
+++ b/bigtop-manager-ui/src/assets/images/svg/user.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,43 +17,9 @@
~ specific language governing permissions and limitations
~ under the License.
-->
-
-<script setup lang="ts">
- import { HomeOutlined } from '@ant-design/icons-vue'
-</script>
-
-<template>
- <a-layout-content class="container">
- <a-breadcrumb class="breadcrumb">
- <a-breadcrumb-item>
- <home-outlined />
- </a-breadcrumb-item>
- <a-breadcrumb-item
- v-for="(item, index) in $route.path.substring(1).split('/')"
- :key="index"
- >
- {{ item }}
- </a-breadcrumb-item>
- </a-breadcrumb>
- <div class="content">
- <router-view />
- </div>
- </a-layout-content>
-</template>
-
-<style scoped lang="scss">
- .container {
- margin: 0 1rem;
- min-height: auto;
-
- .breadcrumb {
- margin: 1rem 0;
- }
-
- .content {
- padding: 1rem;
- border-radius: 0.5rem;
- background: #fff;
- }
- }
-</style>
+<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
+ <circle cx="24" cy="12" r="8" fill="none" stroke="#333" stroke-width="2"
stroke-linecap="round"
+ stroke-linejoin="round" />
+ <path d="M42 44C42 34.0589 33.9411 26 24 26C14.0589 26 6 34.0589 6 44"
stroke="#333" stroke-width="2"
+ stroke-linecap="round" stroke-linejoin="round" />
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/components/chatbot/chat-msg-item.vue
b/bigtop-manager-ui/src/components/chatbot/chat-msg-item.vue
new file mode 100644
index 0000000..2d1ecb5
--- /dev/null
+++ b/bigtop-manager-ui/src/components/chatbot/chat-msg-item.vue
@@ -0,0 +1,104 @@
+<!--
+ ~ 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
new file mode 100644
index 0000000..713d203
--- /dev/null
+++ b/bigtop-manager-ui/src/components/chatbot/chat-window.vue
@@ -0,0 +1,262 @@
+<!--
+ ~ 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,
+ messageReciver,
+ 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: messageReciver.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 reciveMessage = 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()
+ reciveMessage()
+ }
+
+ const handleScrollToBottom = () => {
+ scrollToBottom(document.querySelector('.chat-container') as HTMLElement)
+ }
+
+ onActivated(() => {
+ tempHistory.value = []
+ inputText.value = ''
+ })
+</script>
+
+<template>
+ <div class="platfrom-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>
+ .platfrom-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
new file mode 100644
index 0000000..38d6460
--- /dev/null
+++ b/bigtop-manager-ui/src/components/chatbot/chatbot.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 { 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
new file mode 100644
index 0000000..a15b847
--- /dev/null
+++ b/bigtop-manager-ui/src/components/chatbot/model-selector.vue
@@ -0,0 +1,64 @@
+<!--
+ ~ 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
new file mode 100644
index 0000000..96ec999
--- /dev/null
+++ b/bigtop-manager-ui/src/components/chatbot/platform-auth-form.vue
@@ -0,0 +1,156 @@
+<!--
+ ~ 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.loadingText_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
new file mode 100644
index 0000000..b689fd0
--- /dev/null
+++ b/bigtop-manager-ui/src/components/chatbot/platform-auth-selector.vue
@@ -0,0 +1,126 @@
+<!--
+ ~ 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
new file mode 100644
index 0000000..c5ddb59
--- /dev/null
+++ b/bigtop-manager-ui/src/components/chatbot/platform-selection.vue
@@ -0,0 +1,99 @@
+<!--
+ ~ 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 platformSeletions = 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="platformSeletions" @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
new file mode 100644
index 0000000..335c66d
--- /dev/null
+++ b/bigtop-manager-ui/src/components/chatbot/select-menu.vue
@@ -0,0 +1,157 @@
+<!--
+ ~ 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
new file mode 100644
index 0000000..11443fc
--- /dev/null
+++ b/bigtop-manager-ui/src/components/chatbot/thread-selector.vue
@@ -0,0 +1,157 @@
+<!--
+ ~ 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/index.ts
b/bigtop-manager-ui/src/components/common/index.ts
index ef7b24c..d0b6fcc 100644
--- a/bigtop-manager-ui/src/components/common/index.ts
+++ b/bigtop-manager-ui/src/components/common/index.ts
@@ -20,10 +20,12 @@
import type { App } from 'vue'
import SvgIcon from './svg-icon/index.vue'
import DotState from './dot-state/index.vue'
+import MarkView from './markdown-view/index.vue'
const comments = {
SvgIcon,
- DotState
+ DotState,
+ MarkView
}
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
new file mode 100644
index 0000000..573f6b4
--- /dev/null
+++ b/bigtop-manager-ui/src/components/common/markdown-view/index.vue
@@ -0,0 +1,110 @@
+<!--
+ ~ 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, toRefs, nextTick } from 'vue'
+ import { Marked } from 'marked'
+ import { markedHighlight } from 'marked-highlight'
+ import { copyText } from '@/utils/tools'
+ import { message } from 'ant-design-vue'
+ import { useI18n } from 'vue-i18n'
+ import hljs from 'highlight.js'
+
+ type Props = {
+ markRaw: string
+ }
+
+ const props = withDefaults(defineProps<Props>(), {
+ markRaw: ''
+ })
+
+ const { t } = useI18n()
+ const { markRaw } = toRefs(props)
+ const markdownContent = computed(() => parseMDByHighlight(markRaw.value))
+
+ const copyCode = (code: string) => {
+ copyText(code)
+ .then(() => {
+ message.success(`${t('common.copy_success')}`)
+ })
+ .catch((err: Error) => {
+ message.error(`${t('common.copy_fail')}`)
+ console.log('err :>> ', err)
+ })
+ }
+
+ const upgradeCodeBlock = async (el: HTMLElement) => {
+ await nextTick()
+ const pres = el.querySelectorAll('pre')
+ pres.forEach((pre) => {
+ const code = pre.querySelector('code')
+ 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 || '')
+ )
+ })
+ }
+
+ const upgradedCodeBlock = (parsedMarked: string) => {
+ const upgradeParts = `<pre style="padding:0"><div
class="code-block-head"><label id="language">Code</label><button id="copy">${t(
+ 'common.copy'
+ )}</button></div>`
+ return parsedMarked.replace(/<pre>/g, upgradeParts)
+ }
+
+ const getMarkedHighlightOps = () => {
+ return markedHighlight({
+ langPrefix: 'hljs language-',
+ highlight(code, lang) {
+ const language = hljs.getLanguage(lang) ? lang : 'plaintext'
+ return hljs.highlight(code, { language }).value
+ }
+ })
+ }
+
+ const parseMDByHighlight = (content: string) => {
+ const marked = new Marked(getMarkedHighlightOps())
+ return upgradedCodeBlock(marked.parse(content) as string)
+ }
+
+ const vUpgradeCodeBlock = {
+ updated: upgradeCodeBlock,
+ mounted: upgradeCodeBlock
+ }
+</script>
+
+<template>
+ <div
+ v-upgradeCodeBlock
+ v-dompurify-html="markdownContent"
+ class="markdown-body"
+ >
+ </div>
+</template>
+
+<style lang="scss" scoped></style>
diff --git a/bigtop-manager-ui/src/composables/use-chat-bot.ts
b/bigtop-manager-ui/src/composables/use-chat-bot.ts
new file mode 100644
index 0000000..227b373
--- /dev/null
+++ b/bigtop-manager-ui/src/composables/use-chat-bot.ts
@@ -0,0 +1,241 @@
+/*
+ * 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 messageReciver = 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(platfromInfo: ChatThreadCondition) {
+ try {
+ loading.value = true
+ const data = await createChatThread(platfromInfo)
+ 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) => {
+ messageReciver.value = event.target.responseText
+ .split('data:')
+ .map((s: string) => {
+ return s.trimEnd()
+ })
+ .join('')
+ }
+
+ const onMessageComplete = () => {
+ const formatResultMsg = messageReciver.value
+ messageReciver.value = ''
+ cancelSseConnect()
+ return Promise.resolve({ message: formatResultMsg, state: true })
+ }
+
+ const cancelSseConnect = () => {
+ if (!canceler.value) {
+ return
+ }
+ canceler.value()
+ }
+
+ return {
+ messageReciver,
+ loading,
+ receiving,
+ checkLoading,
+ fetchSupportedPlatforms,
+ fetchCredentialFormModelOfPlatform,
+ testAuthPlatform,
+ fetchCreateChatThread,
+ fetchChatThreads,
+ fetchAuthorizedPlatforms,
+ fetchThreadChatHistory,
+ fetchSendChatMessage,
+ fetchDelAuthorizedPlatform,
+ fetchDelChatThread
+ }
+}
+
+export default useChatBot
diff --git a/bigtop-manager-ui/src/layouts/content.vue
b/bigtop-manager-ui/src/layouts/content.vue
index 1bde145..1093d18 100644
--- a/bigtop-manager-ui/src/layouts/content.vue
+++ b/bigtop-manager-ui/src/layouts/content.vue
@@ -19,6 +19,7 @@
<script setup lang="ts">
import { HomeOutlined } from '@ant-design/icons-vue'
+ import Chatbot from '@/components/chatbot/chatbot.vue'
</script>
<template>
@@ -37,6 +38,7 @@
<div class="content">
<router-view />
</div>
+ <chatbot />
</a-layout-content>
</template>
diff --git a/bigtop-manager-ui/src/locales/en_US/index.ts
b/bigtop-manager-ui/src/locales/en_US/ai.ts
similarity index 57%
copy from bigtop-manager-ui/src/locales/en_US/index.ts
copy to bigtop-manager-ui/src/locales/en_US/ai.ts
index 7fc6f12..6a13d96 100644
--- a/bigtop-manager-ui/src/locales/en_US/index.ts
+++ b/bigtop-manager-ui/src/locales/en_US/ai.ts
@@ -17,20 +17,17 @@
* under the License.
*/
-import common from '@/locales/en_US/common.ts'
-import login from '@/locales/en_US/login'
-import user from '@/locales/en_US/user.ts'
-import cluster from '@/locales/en_US/cluster.ts'
-import hosts from '@/locales/en_US/hosts.ts'
-import service from '@/locales/en_US/service.ts'
-import job from '@/locales/en_US/job.ts'
-
export default {
- common,
- login,
- user,
- cluster,
- hosts,
- service,
- job
+ empty_message: 'Message cannot be empty',
+ select_authorized_platform: 'Please select the following authorized
platform',
+ no_authorized_platform: 'No authorized platforms',
+ or_you_can: 'Or you can',
+ authorize_new_platform: 'Authorize New Platform',
+ select_platform_to_authorize: 'Select platform to authorize',
+ authorizing_platform:
+ ' You are authorizing {0} platform, please fill in the following
information',
+ select_thread_to_chat: 'Please select the thread to enter chat',
+ select_model: 'Please select the model you want to use',
+ create_new_thread: 'Create New Thread',
+ thread_name: 'Thread {0}'
}
diff --git a/bigtop-manager-ui/src/locales/en_US/common.ts
b/bigtop-manager-ui/src/locales/en_US/common.ts
index 920312f..8b33e62 100644
--- a/bigtop-manager-ui/src/locales/en_US/common.ts
+++ b/bigtop-manager-ui/src/locales/en_US/common.ts
@@ -58,5 +58,12 @@ export default {
notification: 'Notification',
copy: 'Copy',
copy_success: 'Copy success!',
- copy_fail: 'Copy failed!'
+ copy_fail: 'Copy failed!',
+ delete_success: 'Delete success!',
+ 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} ?',
+ no_options: 'No options',
+ loadingText_verifying: 'Verifying'
}
diff --git a/bigtop-manager-ui/src/locales/en_US/index.ts
b/bigtop-manager-ui/src/locales/en_US/index.ts
index 7fc6f12..b367ad1 100644
--- a/bigtop-manager-ui/src/locales/en_US/index.ts
+++ b/bigtop-manager-ui/src/locales/en_US/index.ts
@@ -24,6 +24,7 @@ import cluster from '@/locales/en_US/cluster.ts'
import hosts from '@/locales/en_US/hosts.ts'
import service from '@/locales/en_US/service.ts'
import job from '@/locales/en_US/job.ts'
+import ai from '@/locales/en_US/ai.ts'
export default {
common,
@@ -32,5 +33,6 @@ export default {
cluster,
hosts,
service,
- job
+ job,
+ ai
}
diff --git a/bigtop-manager-ui/src/locales/en_US/index.ts
b/bigtop-manager-ui/src/locales/zh_CN/ai.ts
similarity index 59%
copy from bigtop-manager-ui/src/locales/en_US/index.ts
copy to bigtop-manager-ui/src/locales/zh_CN/ai.ts
index 7fc6f12..551f465 100644
--- a/bigtop-manager-ui/src/locales/en_US/index.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/ai.ts
@@ -17,20 +17,16 @@
* under the License.
*/
-import common from '@/locales/en_US/common.ts'
-import login from '@/locales/en_US/login'
-import user from '@/locales/en_US/user.ts'
-import cluster from '@/locales/en_US/cluster.ts'
-import hosts from '@/locales/en_US/hosts.ts'
-import service from '@/locales/en_US/service.ts'
-import job from '@/locales/en_US/job.ts'
-
export default {
- common,
- login,
- user,
- cluster,
- hosts,
- service,
- job
+ empty_message: '消息内容不能为空',
+ select_authorized_platform: '请选择下列已授权的平台',
+ no_authorized_platform: '暂无授权平台',
+ or_you_can: '或者你可以',
+ authorize_new_platform: '授权新平台',
+ select_platform_to_authorize: '请选择下列要授权的平台',
+ authorizing_platform: '您正在授权 {0} 平台, 请填写下列信息',
+ select_thread_to_chat: '请选择下面的线程进入聊天',
+ select_model: '请选择您要使用的模型',
+ create_new_thread: '创建新线程',
+ thread_name: '线程 {0}'
}
diff --git a/bigtop-manager-ui/src/locales/zh_CN/common.ts
b/bigtop-manager-ui/src/locales/zh_CN/common.ts
index 8de97e0..f9694f0 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/common.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/common.ts
@@ -58,5 +58,11 @@ export default {
notification: '通知',
copy: '复制',
copy_success: '复制成功',
- copy_fail: '复制失败'
+ copy_fail: '复制失败',
+ delete_success: '删除成功',
+ delete_fail: '删除失败',
+ delete_confirm_title: '删除确认',
+ delete_confirm_content: '删除后无法恢复,确定要删除 {0} 吗?',
+ no_options: '暂无选项',
+ loadingText_verifying: '校验中'
}
diff --git a/bigtop-manager-ui/src/locales/zh_CN/index.ts
b/bigtop-manager-ui/src/locales/zh_CN/index.ts
index 7ebd07b..fe7d911 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/index.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/index.ts
@@ -24,6 +24,7 @@ import cluster from '@/locales/zh_CN/cluster.ts'
import hosts from '@/locales/zh_CN/hosts.ts'
import service from '@/locales/zh_CN/service.ts'
import job from '@/locales/zh_CN/job.ts'
+import ai from '@/locales/zh_CN/ai.ts'
export default {
common,
@@ -32,5 +33,6 @@ export default {
cluster,
hosts,
service,
- job
+ job,
+ ai
}
diff --git a/bigtop-manager-ui/src/main.ts b/bigtop-manager-ui/src/main.ts
index 92dcea5..6539e20 100644
--- a/bigtop-manager-ui/src/main.ts
+++ b/bigtop-manager-ui/src/main.ts
@@ -21,7 +21,9 @@ import { createApp } from 'vue'
import App from './App.vue'
import plugins from '@/plugins'
-import '@/styles/default.css'
+import '@/styles/main.css'
+import '@/styles/scrollbar.scss'
+import '@/styles/marked.scss'
import 'ant-design-vue/dist/reset.css'
import 'virtual:svg-icons-register'
diff --git a/bigtop-manager-ui/src/plugins/index.ts
b/bigtop-manager-ui/src/plugins/index.ts
index a6a8089..d802617 100644
--- a/bigtop-manager-ui/src/plugins/index.ts
+++ b/bigtop-manager-ui/src/plugins/index.ts
@@ -23,7 +23,7 @@ import pinia from '@/store'
import i18n from '@/locales'
import Antd, { message } from 'ant-design-vue'
import components from '@/components/common'
-
+import VueDOMPurifyHTML from 'vue-dompurify-html'
interface PluginOptions {
antdMessageMaxCount: number
}
@@ -35,6 +35,7 @@ export default {
app.use(pinia)
app.use(i18n)
app.use(components)
+ app.use(VueDOMPurifyHTML) // xss defense
message.config({ maxCount: options.antdMessageMaxCount })
}
}
diff --git a/bigtop-manager-ui/src/styles/function.scss
b/bigtop-manager-ui/src/styles/common/index.scss
similarity index 93%
rename from bigtop-manager-ui/src/styles/function.scss
rename to bigtop-manager-ui/src/styles/common/index.scss
index 61f5439..2edafe0 100644
--- a/bigtop-manager-ui/src/styles/function.scss
+++ b/bigtop-manager-ui/src/styles/common/index.scss
@@ -15,4 +15,7 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- */
\ No newline at end of file
+ */
+
+@import './variables.scss';
+@import './mixins.scss';
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/styles/mixins.scss
b/bigtop-manager-ui/src/styles/common/mixins.scss
similarity index 100%
rename from bigtop-manager-ui/src/styles/mixins.scss
rename to bigtop-manager-ui/src/styles/common/mixins.scss
diff --git a/bigtop-manager-ui/src/styles/variables.scss
b/bigtop-manager-ui/src/styles/common/variables.scss
similarity index 100%
rename from bigtop-manager-ui/src/styles/variables.scss
rename to bigtop-manager-ui/src/styles/common/variables.scss
diff --git a/bigtop-manager-ui/src/styles/default.css
b/bigtop-manager-ui/src/styles/main.css
similarity index 97%
copy from bigtop-manager-ui/src/styles/default.css
copy to bigtop-manager-ui/src/styles/main.css
index dcd70d8..addd8b5 100644
--- a/bigtop-manager-ui/src/styles/default.css
+++ b/bigtop-manager-ui/src/styles/main.css
@@ -17,8 +17,6 @@
* under the License.
*/
-@import './scrollbar.css';
-
* {
outline: 0;
}
diff --git a/bigtop-manager-ui/src/styles/main.scss
b/bigtop-manager-ui/src/styles/main.scss
deleted file mode 100644
index 0dbde25..0000000
--- a/bigtop-manager-ui/src/styles/main.scss
+++ /dev/null
@@ -1,22 +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 '@/styles/function.scss';
-@import '@/styles/variables.scss';
-@import '@/styles/mixins.scss'
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/styles/default.css
b/bigtop-manager-ui/src/styles/marked.scss
similarity index 56%
rename from bigtop-manager-ui/src/styles/default.css
rename to bigtop-manager-ui/src/styles/marked.scss
index dcd70d8..1d6b6cb 100644
--- a/bigtop-manager-ui/src/styles/default.css
+++ b/bigtop-manager-ui/src/styles/marked.scss
@@ -17,42 +17,48 @@
* under the License.
*/
-@import './scrollbar.css';
+@import 'github-markdown-css/github-markdown.css';
+@import 'highlight.js/styles/github-dark.css';
-* {
- outline: 0;
-}
+.markdown-body {
+ background-color: inherit;
+ color: #333333;
+ box-sizing: border-box;
+ font-size: 1em;
-html {
- --text-color: rgba(0, 0, 0, 0.85);
- --text-color-desc: rgba(0, 0, 0, 0.45);
- --bg-color: #fff;
- --hover-color: rgba(0, 0, 0, 0.05);
- --bg-color-container: #f0f2f5;
- --c-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
+ pre {
+ border-radius: 8px;
+ }
}
-.m-0 {
- margin: 0;
+.markdown-body h1,
+.markdown-body h2,
+.markdown-body h3 {
+ color: #000000;
}
-.p-0 {
- padding: 0;
+.markdown-body a {
+ color: #007bff;
}
-.hidden {
- display: none;
+.markdown-body a:hover {
+ text-decoration: underline;
}
-.ant-table-body {
- overflow-y: auto !important;
+.markdown-body ul {
+ list-style-type: disc;
+ margin-left: 10px;
+ margin-bottom: 10px;
}
-#app {
- width: 100%;
- box-sizing: border-box;
- position: fixed;
- top: 0;
- bottom: 0;
- left: 0;
+.markdown-body ul li {
+ margin-bottom: 10px;
}
+
+.code-block-head {
+ background: #4c4d58;
+ display: flex;
+ justify-content: space-between;
+ padding: 6px;
+ align-items: center;
+}
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/styles/scrollbar.css
b/bigtop-manager-ui/src/styles/scrollbar.scss
similarity index 91%
rename from bigtop-manager-ui/src/styles/scrollbar.css
rename to bigtop-manager-ui/src/styles/scrollbar.scss
index 095c5d4..4e38af8 100644
--- a/bigtop-manager-ui/src/styles/scrollbar.css
+++ b/bigtop-manager-ui/src/styles/scrollbar.scss
@@ -16,31 +16,36 @@
* specific language governing permissions and limitations
* under the License.
*/
-
+
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
+
::scrollbar {
width: 6px;
height: 6px;
}
+
::-webkit-scrollbar-track {
display: none;
-webkit-box-shadow: inset 0 0 2px #b18933;
}
+
::scrollbar-track {
display: none;
- -webkit-box-shadow: inset 0 0 2px #98f165;
+ box-shadow: inset 0 0 2px #98f165;
border-radius: 1px;
}
+
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #c1c1c1;
-webkit-box-shadow: inset 0 0 1px rgba(0, 102, 102, 0.144);
}
+
::scrollbar-thumb {
border-radius: 10px;
background: #c1c1c1;
- -webkit-box-shadow: inset 0 0 1px rgba(0, 102, 102, 0.144);
-}
+ 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/locales/en_US/index.ts
b/bigtop-manager-ui/src/utils/render.ts
similarity index 63%
copy from bigtop-manager-ui/src/locales/en_US/index.ts
copy to bigtop-manager-ui/src/utils/render.ts
index 7fc6f12..6220d47 100644
--- a/bigtop-manager-ui/src/locales/en_US/index.ts
+++ b/bigtop-manager-ui/src/utils/render.ts
@@ -17,20 +17,19 @@
* under the License.
*/
-import common from '@/locales/en_US/common.ts'
-import login from '@/locales/en_US/login'
-import user from '@/locales/en_US/user.ts'
-import cluster from '@/locales/en_US/cluster.ts'
-import hosts from '@/locales/en_US/hosts.ts'
-import service from '@/locales/en_US/service.ts'
-import job from '@/locales/en_US/job.ts'
+import { Marked } from 'marked'
+import { markedHighlight } from 'marked-highlight'
+import hljs from 'highlight.js'
-export default {
- common,
- login,
- user,
- cluster,
- hosts,
- service,
- job
+export const parseMDByHighlight = (content: string) => {
+ const marked = new Marked(
+ markedHighlight({
+ langPrefix: 'hljs language-',
+ highlight(code, lang) {
+ const language = hljs.getLanguage(lang) ? lang : 'plaintext'
+ return hljs.highlight(code, { language }).value
+ }
+ })
+ )
+ return marked.parse(content)
}
diff --git a/bigtop-manager-ui/vite.config.ts b/bigtop-manager-ui/vite.config.ts
index ff4ffe6..50a2b2a 100644
--- a/bigtop-manager-ui/vite.config.ts
+++ b/bigtop-manager-ui/vite.config.ts
@@ -47,7 +47,7 @@ export default defineConfig(({ mode }) => {
css: {
preprocessorOptions: {
scss: {
- additionalData: '@import "@/styles/main.scss";'
+ additionalData: '@import "@/styles/common/index.scss";'
}
}
},