feat chat ui (#3253)

This commit is contained in:
shaohuzhang1 2025-06-13 16:17:02 +08:00 committed by GitHub
parent 4948051f67
commit 9558e4053d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 197 additions and 10 deletions

View File

@ -175,8 +175,7 @@ class Workflow:
return Workflow(nodes, edges)
def get_start_node(self):
start_node_list = [node for node in self.nodes if node.id == 'start-node']
return start_node_list[0]
return self.get_node('start-node')
def get_search_node(self):
return [node for node in self.nodes if node.type == 'search-dataset-node']

14
ui/chat.html Normal file
View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base target="_blank" />
<title>%VITE_APP_TITLE%</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/chat.ts"></script>
</body>
</html>

3
ui/env/.env vendored
View File

@ -1,4 +1,5 @@
VITE_APP_NAME=ui
VITE_BASE_PATH=/ui/
VITE_APP_PORT=3000
VITE_APP_TITLE = 'MaxKB'
VITE_APP_TITLE = 'MaxKB'
VITE_INPUT="index.html"

5
ui/env/.env.chat vendored Normal file
View File

@ -0,0 +1,5 @@
VITE_APP_NAME=chat
VITE_BASE_PATH=/chat/
VITE_APP_PORT=3000
VITE_APP_TITLE = 'MaxKB'
VITE_INPUT="chat.html"

View File

@ -5,9 +5,12 @@
"type": "module",
"scripts": {
"dev": "vite",
"chat": "vite --mode chat",
"build": "run-p type-check \"build-only {@}\" --",
"build-chat": "run-p type-check \"build-only-chat {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"build-only-chat": "vite build --mode chat",
"type-check": "vue-tsc --build",
"lint": "eslint . --fix",
"format": "prettier --write src/"
@ -39,6 +42,7 @@
"screenfull": "^6.0.2",
"sortablejs": "^1.15.6",
"use-element-plus-theme": "^0.0.5",
"vite-plugin-html": "^3.2.2",
"vue": "^3.5.13",
"vue-clipboard3": "^2.0.0",
"vue-codemirror": "^6.1.1",

65
ui/src/chat.ts Normal file
View File

@ -0,0 +1,65 @@
import '@/styles/index.scss'
import ElementPlus from 'element-plus'
import * as ElementPlusIcons from '@element-plus/icons-vue'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import enUs from 'element-plus/es/locale/lang/en'
import zhTW from 'element-plus/es/locale/lang/zh-tw'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from '@/router/chat'
import i18n from '@/locales'
import Components from '@/components'
import directives from '@/directives'
import { config } from 'md-editor-v3'
import screenfull from 'screenfull'
import katex from 'katex'
import 'katex/dist/katex.min.css'
import Cropper from 'cropperjs'
import mermaid from 'mermaid'
import highlight from 'highlight.js'
import 'highlight.js/styles/atom-one-dark.css'
config({
editorExtensions: {
highlight: {
instance: highlight,
},
screenfull: {
instance: screenfull,
},
katex: {
instance: katex,
},
cropper: {
instance: Cropper,
},
mermaid: {
instance: mermaid,
},
},
})
const app = createApp(App)
app.use(createPinia())
for (const [key, component] of Object.entries(ElementPlusIcons)) {
app.component(key, component)
}
const locale_map: any = {
'zh-CN': zhCn,
'zh-Hant': zhTW,
'en-US': enUs,
}
app.use(ElementPlus, {
locale: locale_map[localStorage.getItem('MaxKB-locale') || navigator.language || 'en-US'],
})
app.use(directives)
app.use(router)
app.use(i18n)
app.use(Components)
app.mount('#app')
export { app }

View File

@ -0,0 +1,82 @@
import { hasPermission } from '@/utils/permission/index'
import NProgress from 'nprogress'
import {
createRouter,
createWebHistory,
type NavigationGuardNext,
type RouteLocationNormalized,
type RouteRecordRaw,
type RouteRecordName,
} from 'vue-router'
import useStore from '@/stores'
import { routes } from '@/router/chat/routes'
NProgress.configure({ showSpinner: false, speed: 500, minimum: 0.3 })
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: routes,
})
// 路由前置拦截器
router.beforeEach(
async (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
NProgress.start()
if (to.name === '404') {
next()
return
}
const { user, login } = useStore()
const notAuthRouteNameList = ['login', 'ForgotPassword', 'ResetPassword', 'Chat', 'UserLogin']
if (!notAuthRouteNameList.includes(to.name ? to.name.toString() : '')) {
if (to.query && to.query.token) {
localStorage.setItem('token', to.query.token.toString())
}
const token = login.getToken()
if (!token) {
next({
path: '/login',
})
return
}
if (!user.userInfo) {
await user.profile()
}
}
// 判断是否有菜单权限
if (to.meta.permission ? hasPermission(to.meta.permission as any, 'OR') : true) {
next()
} else {
// 如果没有权限则直接取404页面
next('404')
}
},
)
router.afterEach(() => {
NProgress.done()
})
export const getChildRouteListByPathAndName = (path: any, name?: RouteRecordName | any) => {
return getChildRouteList(routes, path, name)
}
export const getChildRouteList: (
routeList: Array<RouteRecordRaw>,
path: string,
name?: RouteRecordName | null | undefined,
) => Array<RouteRecordRaw> = (routeList, path, name) => {
for (let index = 0; index < routeList.length; index++) {
const route = routeList[index]
if (name === route.name && path === route.path) {
return route.children || []
}
if (route.children && route.children.length > 0) {
const result = getChildRouteList(route.children, path, name)
if (result && result?.length > 0) {
return result
}
}
}
return []
}
export default router

View File

@ -0,0 +1,10 @@
import type { RouteRecordRaw } from 'vue-router'
export const routes: Array<RouteRecordRaw> = [
// 对话
{
path: '/chat/:accessToken',
name: 'Chat',
component: () => import('@/views/chat/index.vue'),
},
]

View File

@ -4,40 +4,44 @@ import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import DefineOptions from 'unplugin-vue-define-options/vite'
import path from 'path'
import { createHtmlPlugin } from 'vite-plugin-html'
// import vueDevTools from 'vite-plugin-vue-devtools'
const envDir = './env'
// https://vite.dev/config/
export default defineConfig(({ mode }) => {
console.log('ssss')
const ENV = loadEnv(mode, envDir)
const prefix = process.env.VITE_DYNAMIC_PREFIX || ENV.VITE_BASE_PATH
const proxyConf: Record<string, string | ProxyOptions> = {}
proxyConf['/api'] = {
target: 'http://127.0.0.1:8080',
changeOrigin: true,
rewrite: (path) => path.replace(ENV.VITE_BASE_PATH, '/'),
rewrite: (path: string) => path.replace(ENV.VITE_BASE_PATH, '/'),
}
proxyConf['/oss'] = {
proxyConf['/oss'] = {
target: 'http://127.0.0.1:8080',
changeOrigin: true,
rewrite: (path) => path.replace(ENV.VITE_BASE_PATH, '/'),
rewrite: (path: string) => path.replace(ENV.VITE_BASE_PATH, '/'),
}
proxyConf['/doc'] = {
target: 'http://127.0.0.1:8080',
changeOrigin: true,
rewrite: (path) => path.replace(ENV.VITE_BASE_PATH, '/'),
rewrite: (path: string) => path.replace(ENV.VITE_BASE_PATH, '/'),
}
proxyConf['/static'] = {
target: 'http://127.0.0.1:8080',
changeOrigin: true,
rewrite: (path) => path.replace(ENV.VITE_BASE_PATH, '/'),
rewrite: (path: string) => path.replace(ENV.VITE_BASE_PATH, '/'),
}
return {
preflight: false,
lintOnSave: false,
base: prefix,
envDir: envDir,
plugins: [vue(), vueJsx(), DefineOptions()],
plugins: [vue(), vueJsx(), DefineOptions(), createHtmlPlugin({ template: ENV.VITE_INPUT })],
server: {
cors: true,
host: '0.0.0.0',
@ -46,7 +50,10 @@ export default defineConfig(({ mode }) => {
proxy: proxyConf,
},
build: {
outDir: 'dist/ui',
outDir: `dist${ENV.VITE_BASE_PATH}`,
rollupOptions: {
input: path.resolve(__dirname, ENV.VITE_INPUT),
},
},
resolve: {
alias: {