Browse Source

feat: MindPilotUI

main
gjl 1 year ago
parent
commit
146f6edd4e
75 changed files with 14850 additions and 0 deletions
  1. +9
    -0
      MindpilotUI-1/.editorconfig
  2. +4
    -0
      MindpilotUI-1/.eslintignore
  3. +17
    -0
      MindpilotUI-1/.eslintrc.cjs
  4. +10
    -0
      MindpilotUI-1/.gitignore
  5. +6
    -0
      MindpilotUI-1/.prettierignore
  6. +4
    -0
      MindpilotUI-1/.prettierrc.yaml
  7. +3
    -0
      MindpilotUI-1/dev-app-update.yml
  8. +67
    -0
      MindpilotUI-1/electron-builder.json
  9. +28
    -0
      MindpilotUI-1/electron.vite.config.ts
  10. +73
    -0
      MindpilotUI-1/package.json
  11. BIN
      MindpilotUI-1/resources/icon.png
  12. +77
    -0
      MindpilotUI-1/src/main/index.ts
  13. +22
    -0
      MindpilotUI-1/src/preload/index.ts
  14. +13
    -0
      MindpilotUI-1/src/renderer/index.html
  15. +6
    -0
      MindpilotUI-1/src/renderer/src/App.vue
  16. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/history.svg
  17. BIN
      MindpilotUI-1/src/renderer/src/assets/material-symbols--upload-sharp.png
  18. BIN
      MindpilotUI-1/src/renderer/src/assets/mindlabicon.png
  19. BIN
      MindpilotUI-1/src/renderer/src/assets/mingcute--tool-line.png
  20. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/simple-line-icons--options.svg
  21. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/back.svg
  22. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/back_top.svg
  23. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/calendar.svg
  24. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/dark.svg
  25. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/day.svg
  26. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/enter_outlined.svg
  27. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/exit_screen.svg
  28. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/full_screen.svg
  29. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/globalization.svg
  30. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/hot.svg
  31. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/keyboard_esc.svg
  32. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/laptop.svg
  33. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/service.svg
  34. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/shop.svg
  35. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/system.svg
  36. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/svg/user_avatar.svg
  37. +1
    -0
      MindpilotUI-1/src/renderer/src/assets/weui--back-outlined.svg
  38. +4
    -0
      MindpilotUI-1/src/renderer/src/assets/zhipu.svg
  39. +191
    -0
      MindpilotUI-1/src/renderer/src/components/ListCard.vue
  40. +3869
    -0
      MindpilotUI-1/src/renderer/src/components/ReIcon/data.ts
  41. +15
    -0
      MindpilotUI-1/src/renderer/src/components/ReIcon/index.ts
  42. +270
    -0
      MindpilotUI-1/src/renderer/src/components/ReIcon/src/Select.vue
  43. +63
    -0
      MindpilotUI-1/src/renderer/src/components/ReIcon/src/hooks.ts
  44. +48
    -0
      MindpilotUI-1/src/renderer/src/components/ReIcon/src/iconfont.ts
  45. +33
    -0
      MindpilotUI-1/src/renderer/src/components/ReIcon/src/iconifyIconOffline.ts
  46. +30
    -0
      MindpilotUI-1/src/renderer/src/components/ReIcon/src/iconifyIconOnline.ts
  47. +70
    -0
      MindpilotUI-1/src/renderer/src/components/ReIcon/src/offlineIcon.ts
  48. +20
    -0
      MindpilotUI-1/src/renderer/src/components/ReIcon/src/types.ts
  49. +7
    -0
      MindpilotUI-1/src/renderer/src/components/ReText/index.ts
  50. +68
    -0
      MindpilotUI-1/src/renderer/src/components/ReText/src/index.vue
  51. +13
    -0
      MindpilotUI-1/src/renderer/src/components/Versions.vue
  52. +123
    -0
      MindpilotUI-1/src/renderer/src/components/fileCard.vue
  53. +15
    -0
      MindpilotUI-1/src/renderer/src/env.d.ts
  54. +21
    -0
      MindpilotUI-1/src/renderer/src/main.ts
  55. +32
    -0
      MindpilotUI-1/src/renderer/src/router/index.ts
  56. +28
    -0
      MindpilotUI-1/src/renderer/src/store/store.ts
  57. +10
    -0
      MindpilotUI-1/src/renderer/src/utils/tools.ts
  58. +686
    -0
      MindpilotUI-1/src/renderer/src/views/agentconfig.vue
  59. +214
    -0
      MindpilotUI-1/src/renderer/src/views/configManagement.ts
  60. +343
    -0
      MindpilotUI-1/src/renderer/src/views/conversationApi.ts
  61. +67
    -0
      MindpilotUI-1/src/renderer/src/views/conversationSummary.ts
  62. +153
    -0
      MindpilotUI-1/src/renderer/src/views/customComponents.ts
  63. +1092
    -0
      MindpilotUI-1/src/renderer/src/views/home.vue
  64. +363
    -0
      MindpilotUI-1/src/renderer/src/views/knowledgebase/example.ts
  65. +302
    -0
      MindpilotUI-1/src/renderer/src/views/knowledgebase/filelistdialog.vue
  66. +280
    -0
      MindpilotUI-1/src/renderer/src/views/knowledgebase/kbapi.ts
  67. +261
    -0
      MindpilotUI-1/src/renderer/src/views/knowledgebase/kbconfig.vue
  68. +74
    -0
      MindpilotUI-1/src/renderer/src/views/knowledgebase/newkbdialog.vue
  69. +34
    -0
      MindpilotUI-1/src/renderer/src/views/toolConfig.ts
  70. +37
    -0
      MindpilotUI-1/src/renderer/src/views/type.ts
  71. +32
    -0
      MindpilotUI-1/src/renderer/src/views/utils.ts
  72. +4
    -0
      MindpilotUI-1/tsconfig.json
  73. +8
    -0
      MindpilotUI-1/tsconfig.node.json
  74. +18
    -0
      MindpilotUI-1/tsconfig.web.json
  75. +5594
    -0
      MindpilotUI-1/yarn.lock

+ 9
- 0
MindpilotUI-1/.editorconfig View File

@@ -0,0 +1,9 @@
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

+ 4
- 0
MindpilotUI-1/.eslintignore View File

@@ -0,0 +1,4 @@
node_modules
dist
out
.gitignore

+ 17
- 0
MindpilotUI-1/.eslintrc.cjs View File

@@ -0,0 +1,17 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')

module.exports = {
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'@electron-toolkit',
'@electron-toolkit/eslint-config-ts/eslint-recommended',
'@vue/eslint-config-typescript/recommended',
'@vue/eslint-config-prettier'
],
rules: {
'vue/require-default-prop': 'off',
'vue/multi-word-component-names': 'off'
}
}

+ 10
- 0
MindpilotUI-1/.gitignore View File

@@ -0,0 +1,10 @@
node_modules
dist
out
.DS_Store
*.log*

# 项目排除路径
/src/preload/index.d.ts
/.idea
/.vscode

+ 6
- 0
MindpilotUI-1/.prettierignore View File

@@ -0,0 +1,6 @@
out
dist
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json

+ 4
- 0
MindpilotUI-1/.prettierrc.yaml View File

@@ -0,0 +1,4 @@
singleQuote: true
semi: false
printWidth: 100
trailingComma: none

+ 3
- 0
MindpilotUI-1/dev-app-update.yml View File

@@ -0,0 +1,3 @@
provider: generic
url: https://example.com/auto-updates
updaterCacheDirName: mindpilotui-updater

+ 67
- 0
MindpilotUI-1/electron-builder.json View File

@@ -0,0 +1,67 @@
{
"appId": "com.electron.app",
"productName": "mindpilotui",
"directories": {
"buildResources": "build"
},
"files": [
"!**/.vscode/*",
"!src/*",
"!electron.vite.config.{js,ts,mjs,cjs}",
"!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}",
"!{.env,.env.*,.npmrc,pnpm-lock.yaml}",
"!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}"
],
"asarUnpack": [
"resources/**"
],
"win": {
"executableName": "mindpilotui"
},
"nsis": {
"artifactName": "${name}-${version}-setup.${ext}",
"shortcutName": "${productName}",
"uninstallDisplayName": "${productName}",
"createDesktopShortcut": "always",
"allowToChangeInstallationDirectory": true,
"oneClick": false
},
"mac": {
"entitlementsInherit": "build/entitlements.mac.plist",
"extendInfo": [
{
"NSCameraUsageDescription": "Application requests access to the device's camera."
},
{
"NSMicrophoneUsageDescription": "Application requests access to the device's microphone."
},
{
"NSDocumentsFolderUsageDescription": "Application requests access to the user's Documents folder."
},
{
"NSDownloadsFolderUsageDescription": "Application requests access to the user's Downloads folder."
}
],
"notarize": false
},
"dmg": {
"artifactName": "${name}-${version}.${ext}"
},
"linux": {
"target": [
"AppImage",
"snap",
"deb"
],
"maintainer": "electronjs.org",
"category": "Utility"
},
"appImage": {
"artifactName": "${name}-${version}.${ext}"
},
"npmRebuild": false,
"publish": {
"provider": "generic",
"url": "https://example.com/auto-updates"
}
}

+ 28
- 0
MindpilotUI-1/electron.vite.config.ts View File

@@ -0,0 +1,28 @@
import { resolve } from "path";
import { defineConfig, externalizeDepsPlugin } from "electron-vite";
import vue from "@vitejs/plugin-vue";
import svgLoader from "vite-svg-loader";

export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()]
},
preload: {
plugins: [externalizeDepsPlugin()]
},
renderer: {
resolve: {
alias: {
"@renderer": resolve("src/renderer/src")
}
},
plugins: [
vue(),
svgLoader({
svgoConfig: {
multipass: true
}
})
]
}
});

+ 73
- 0
MindpilotUI-1/package.json View File

@@ -0,0 +1,73 @@
{
"name": "mindpilotui",
"version": "1.0.0",
"description": "An Electron application with Vue and TypeScript",
"main": "./out/main/index.js",
"author": "example.com",
"homepage": "https://electron-vite.org",
"scripts": {
"format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "npm run typecheck:node && npm run typecheck:web",
"start": "electron-vite preview",
"dev": "electron-vite dev",
"build": "npm run typecheck && electron-vite build",
"postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win --config ./electron-builder.json",
"build:mac": "npm run build && electron-builder --mac --config ./electron-builder.json",
"build:linux": "npm run build && electron-builder --linux --config ./electron-builder.json"
},
"dependencies": {
"@electron-toolkit/preload": "^3.0.0",
"@electron-toolkit/utils": "^3.0.0",
"@element-plus/icons-vue": "^2.3.1",
"@iconify-icons/ep": "^1.2.12",
"@iconify-icons/ri": "^1.2.10",
"@iconify/utils": "^2.1.25",
"@pureadmin/utils": "^2.4.7",
"axios": "^1.7.3",
"deep-chat": "^2.0.0",
"electron-updater": "^6.1.7",
"element-plus": "^2.7.6",
"idb": "^8.0.0",
"pinia": "^2.2.2",
"uuid": "^10.0.0",
"v-contextmenu": "3.2.0",
"vite-svg-loader": "^5.1.0",
"vue-router": "4",
"vue-runtime-helpers": "^1.1.2",
"vue-tippy": "v6"
},
"devDependencies": {
"@electron-forge/cli": "^6.2.1",
"@electron-forge/maker-deb": "^6.2.1",
"@electron-forge/maker-rpm": "^6.2.1",
"@electron-forge/maker-squirrel": "^6.2.1",
"@electron-forge/maker-zip": "^6.2.1",
"@electron-toolkit/eslint-config": "^1.0.2",
"@electron-toolkit/eslint-config-ts": "^2.0.0",
"@electron-toolkit/tsconfig": "^1.0.1",
"@iconify/vue": "^4.1.2",
"@rushstack/eslint-patch": "^1.10.3",
"@types/node": "^22.5.1",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"electron": "^31.0.2",
"electron-builder": "^24.13.3",
"electron-vite": "^2.3.0",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.26.0",
"prettier": "^3.3.2",
"sass": "^1.77.8",
"typescript": "^5.5.2",
"vite": "^5.3.1",
"vue": "^3.4.30",
"vue-tsc": "^2.0.22"
},
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
}

BIN
MindpilotUI-1/resources/icon.png View File

Before After
Width: 512  |  Height: 512  |  Size: 36 kB

+ 77
- 0
MindpilotUI-1/src/main/index.ts View File

@@ -0,0 +1,77 @@
import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'

function createWindow(): void {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1920,
height: 1080,
show: false,
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
webSecurity: false
}
})

// mainWindow.webContents.toggleDevTools()

mainWindow.on('ready-to-show', () => {
mainWindow.show()
})

mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})

// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')

// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})

// IPC test
ipcMain.on('ping', () => console.log('pong'))

createWindow()

app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})

// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.

+ 22
- 0
MindpilotUI-1/src/preload/index.ts View File

@@ -0,0 +1,22 @@
import { contextBridge } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'

// Custom APIs for renderer
const api = {}

// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)
}
} else {
// @ts-ignore (define in dts)
window.electron = electronAPI
// @ts-ignore (define in dts)
window.api = api
}

+ 13
- 0
MindpilotUI-1/src/renderer/index.html View File

@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Electron</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
</head>

<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

+ 6
- 0
MindpilotUI-1/src/renderer/src/App.vue View File

@@ -0,0 +1,6 @@
<script setup lang="ts">
</script>

<template>
<RouterView />
</template>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/history.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M12 21q-3.45 0-6.012-2.287T3.05 13H5.1q.35 2.6 2.313 4.3T12 19q2.925 0 4.963-2.037T19 12t-2.037-4.962T12 5q-1.725 0-3.225.8T6.25 8H9v2H3V4h2v2.35q1.275-1.6 3.113-2.475T12 3q1.875 0 3.513.713t2.85 1.924t1.925 2.85T21 12t-.712 3.513t-1.925 2.85t-2.85 1.925T12 21m2.8-4.8L11 12.4V7h2v4.6l3.2 3.2z"/></svg>

BIN
MindpilotUI-1/src/renderer/src/assets/material-symbols--upload-sharp.png View File

Before After
Width: 32  |  Height: 32  |  Size: 330 B

BIN
MindpilotUI-1/src/renderer/src/assets/mindlabicon.png View File

Before After
Width: 280  |  Height: 280  |  Size: 48 kB

BIN
MindpilotUI-1/src/renderer/src/assets/mingcute--tool-line.png View File

Before After
Width: 512  |  Height: 512  |  Size: 26 kB

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/simple-line-icons--options.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="#4d4d4d" d="M899.4 638.2h-27.198c-2.2-.6-4.2-1.6-6.4-2c-57.2-8.8-102.4-56.4-106.2-112.199c-4.401-62.4 31.199-115.2 89.199-132.4c7.6-2.2 15.6-3.8 23.399-5.8h27.2c1.8.6 3.4 1.6 5.4 1.8c52.8 8.6 93 46.6 104.4 98.6c.8 4 2 8 3 12v27.2c-.6 1.8-1.6 3.6-1.8 5.4c-8.4 52-45.4 91.599-96.801 103.6c-5 1.2-9.6 2.6-14.2 3.8zM130.603 385.8l27.202.001c2.2.6 4.2 1.6 6.4 1.8c57.6 9 102.6 56.8 106.2 113.2c4 62.2-32 114.8-90.2 131.8c-7.401 2.2-15 3.8-22.401 5.6h-27.2c-1.8-.6-3.4-1.6-5.2-2c-52-9.6-86-39.8-102.2-90.2c-2.2-6.6-3.4-13.6-5.2-20.4v-27.2c.6-1.8 1.6-3.6 1.8-5.4c8.6-52.2 45.4-91.6 96.8-103.6c4.8-1.201 9.4-2.401 13.999-3.601m370.801.001h27.2c2.2.6 4.2 1.6 6.4 2c57.4 9 103.6 58.6 106 114.6c2.8 63-35.2 116.4-93.8 131.4c-6.2 1.6-12.4 3-18.6 4.4h-27.2c-2.2-.6-4.2-1.6-6.4-2c-57.4-8.8-103.601-58.6-106.2-114.6c-3-63 35.2-116.4 93.8-131.4c6.4-1.6 12.6-3 18.8-4.4"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/back.svg View File

@@ -0,0 +1 @@
<svg width="32" height="32" viewBox="0 0 48 48"><path fill="#2F88FF" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width="4" d="M44 40.836q-7.34-8.96-13.036-10.168t-10.846-.365V41L4 23.545 20.118 7v10.167q9.523.075 16.192 6.833 6.668 6.758 7.69 16.836Z" clip-rule="evenodd"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/back_top.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M2.88 18.054a35.9 35.9 0 0 1 8.531-16.32.8.8 0 0 1 1.178 0q.25.27.413.455a35.9 35.9 0 0 1 8.118 15.865c-2.141.451-4.34.747-6.584.874l-2.089 4.178a.5.5 0 0 1-.894 0l-2.089-4.178a44 44 0 0 1-6.584-.874m6.698-1.123 1.157.066L12 19.527l1.265-2.53 1.157-.066a42 42 0 0 0 4.227-.454A33.9 33.9 0 0 0 12 4.09a33.9 33.9 0 0 0-6.649 12.387q2.093.334 4.227.454M12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6m0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/calendar.svg View File

@@ -0,0 +1 @@
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-calendar" viewBox="0 0 16 16"><path fill="currentColor" d="M10 3H6V1.5H5V3H3a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-2V1.5h-1zM5 5h1V4h4v1h1V4h2v2H3V4h2zM3 7h10v6H3z"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/dark.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.38 2.019a7.5 7.5 0 1 0 10.6 10.6C21.662 17.854 17.316 22 12.001 22 6.477 22 2 17.523 2 12c0-5.315 4.146-9.661 9.38-9.981"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/day.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12M11 1h2v3h-2zm0 19h2v3h-2zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414zm2.121-14.85 1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414zM23 11v2h-3v-2zM4 11v2H1v-2z"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/enter_outlined.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="iconify iconify--ant-design" viewBox="0 0 1024 1024"><path fill="currentColor" d="M864 170h-60c-4.4 0-8 3.6-8 8v518H310v-73c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 0 0 0 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-75h498c35.3 0 64-28.7 64-64V178c0-4.4-3.6-8-8-8"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/exit_screen.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" aria-hidden="true" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3.5 4H1V3h2V1h1v2.5zM13 3V1h-1v2.5l.5.5H15V3zm-1 9.5V15h1v-2h2v-1h-2.5zM1 12v1h2v2h1v-2.5l-.5-.5zm11-1.5-.5.5h-7l-.5-.5v-5l.5-.5h7l.5.5zM10 7H6v2h4z"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/full_screen.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" aria-hidden="true" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3 12h10V4H3zm2-6h6v4H5zM2 6H1V2.5l.5-.5H5v1H2zm13-3.5V6h-1V3h-3V2h3.5zM14 10h1v3.5l-.5.5H11v-1h3zM2 13h3v1H1.5l-.5-.5V10h1z"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/globalization.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" aria-hidden="true" class="globalization" viewBox="0 0 512 512"><path fill="currentColor" d="m478.33 433.6-90-218a22 22 0 0 0-40.67 0l-90 218a22 22 0 1 0 40.67 16.79L316.66 406h102.67l18.33 44.39A22 22 0 0 0 458 464a22 22 0 0 0 20.32-30.4zM334.83 362 368 281.65 401.17 362zm-66.99-19.08a22 22 0 0 0-4.89-30.7c-.2-.15-15-11.13-36.49-34.73 39.65-53.68 62.11-114.75 71.27-143.49H330a22 22 0 0 0 0-44H214V70a22 22 0 0 0-44 0v20H54a22 22 0 0 0 0 44h197.25c-9.52 26.95-27.05 69.5-53.79 108.36-31.41-41.68-43.08-68.65-43.17-68.87a22 22 0 0 0-40.58 17c.58 1.38 14.55 34.23 52.86 83.93.92 1.19 1.83 2.35 2.74 3.51-39.24 44.35-77.74 71.86-93.85 80.74a22 22 0 1 0 21.07 38.63c2.16-1.18 48.6-26.89 101.63-85.59 22.52 24.08 38 35.44 38.93 36.1a22 22 0 0 0 30.75-4.9z"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/hot.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 1024 1024"><path fill="#FF5D50" d="M428.698 107.315c-6.503 72.192-36.352 207.258-160.256 337.408 3.686-48.025-7.117-83.763-19.047-107.673-6.605-13.159-26.06-10.599-28.877 3.84-5.734 29.44-20.582 75.059-57.6 137.779-71.628 121.395-62.566 459.878 340.736 459.878S934.093 585.728 876.8 442.522c-37.376-93.44-93.952-152.525-128.82-182.324-11.417-9.779-29.132-1.945-29.593 13.056-.921 30.464-7.321 73.37-33.075 102.144-.666-52.787-38.144-208.384-202.445-296.857-23.296-12.544-51.763 2.457-54.17 28.774z"/><path fill="#FFDF99" d="M702.26 678.4c-4.2-45.056-60.673-166.554-212.634-246.426-10.599-5.58-23.092 3.124-21.504 15.002 6.246 46.848 12.953 140.493-24.064 184.73 4.044-40.397-18.125-73.83-36.66-94.31-8.396-9.217-23.552-4.66-25.497 7.68-3.533 22.322-12.851 56.268-36.557 97.945-42.086 74.035-86.989 188.672 124.57 294.656 10.956.563 22.17.87 33.74.87a618 618 0 0 0 32.717-.87C694.631 878.182 709.837 759.706 702.26 678.4"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/keyboard_esc.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="iconify iconify--mdi" viewBox="0 0 24 24"><path fill="currentColor" d="M1 7h6v2H3v2h4v2H3v2h4v2H1zm10 0h4v2h-4v2h2a2 2 0 0 1 2 2v2c0 1.11-.89 2-2 2H9v-2h4v-2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2m8 0h2a2 2 0 0 1 2 2v1h-2V9h-2v6h2v-1h2v1c0 1.11-.89 2-2 2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/laptop.svg View File

@@ -0,0 +1 @@
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-laptop" viewBox="0 0 16 16"><path fill="currentColor" d="M2.5 12a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h11a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1zm0-1h11V4h-11zM15 13H1v1h14z"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/service.svg View File

@@ -0,0 +1 @@
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-service" viewBox="0 0 16 16"><path fill="currentColor" d="M2.52 6.37a5.5 5.5 0 0 1 10.98.13v4c0 .05 0 .1-.02.15A4.5 4.5 0 0 1 9 14.7H8v-1h1a3.5 3.5 0 0 0 3.4-2.7h-1.9a.5.5 0 0 1-.5-.5v-4c0-.28.22-.5.5-.5h1.93a4.5 4.5 0 0 0-8.86 0H5.5c.28 0 .5.22.5.5v4a.5.5 0 0 1-.5.5H3a.5.5 0 0 1-.5-.5v-4c0-.04 0-.09.02-.13M12.5 7H11v3h1.5zm-9 0v3H5V7z"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/shop.svg View File

@@ -0,0 +1 @@
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-shop" viewBox="0 0 16 16"><path fill="currentColor" d="M8 1a2.5 2.5 0 0 0-2.5 2.5V5h-2a.5.5 0 0 0-.5.5v9c0 .28.22.5.5.5h9a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-2V3.5A2.5 2.5 0 0 0 8 1m1.5 5v2h1V6H12v8H4V6h1.5v2h1V6zm0-1h-3V3.5a1.5 1.5 0 1 1 3 0z"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/system.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="icon" viewBox="0 0 1024 1024"><path d="M554 849.574c0 23.365-18.635 42.307-42 42.307s-42-18.941-42-42.307V662.719c0-23.365 18.635-42.307 42-42.307v-7.051c23.365 0 42 25.993 42 49.358z"/><path d="M893 888.5c0 17.397-14.103 31.5-31.5 31.5h-700c-17.397 0-31.5-14.103-31.5-31.5s14.103-31.5 31.5-31.5h700c17.397 0 31.5 14.103 31.5 31.5m33-714.074C926 135.484 894.686 105 855.744 105H168.256C129.314 105 98 135.484 98 174.426V533h828zM98 630.988C98 669.931 129.314 702 168.256 702h687.488C894.686 702 926 669.931 926 630.988V596H98z"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/svg/user_avatar.svg View File

@@ -0,0 +1 @@
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-user-avatar" viewBox="0 0 16 16"><path fill="currentColor" d="M8 10.5c1.24 0 2.42.31 3.5.88v1.12h1v-1.14a.94.94 0 0 0-.49-.84 8.48 8.48 0 0 0-8.02 0 .94.94 0 0 0-.49.84v1.14h1v-1.12A7.5 7.5 0 0 1 8 10.5M10.5 6a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0m-1 0a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0"/><path fill="currentColor" d="M2.5 1.5a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-11a1 1 0 0 0-1-1zm11 1v11h-11v-11z"/></svg>

+ 1
- 0
MindpilotUI-1/src/renderer/src/assets/weui--back-outlined.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="0.5em" height="1em" viewBox="0 0 12 24"><path fill="#706161" fill-rule="evenodd" d="M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5L10 4.563L2.682 12z"/></svg>

+ 4
- 0
MindpilotUI-1/src/renderer/src/assets/zhipu.svg View File

@@ -0,0 +1,4 @@
<!-- sample rectangle -->
<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#icon-pure-logo_a)" fill="#1665FF"><path d="M15 32a.377.377 0 0 0 .377-.375.377.377 0 0 0-.378-.375.377.377 0 0 0-.378.375c0 .207.17.375.378.375Zm3.907-20.375a2.26 2.26 0 0 0 2.27-2.25 2.26 2.26 0 0 0-2.27-2.25 2.26 2.26 0 0 0-2.269 2.25 2.26 2.26 0 0 0 2.27 2.25Zm3.908 6.625a2.26 2.26 0 0 0 2.27-2.25 2.26 2.26 0 0 0-2.27-2.25A2.26 2.26 0 0 0 20.546 16a2.26 2.26 0 0 0 2.27 2.25Zm-15.63 0A2.26 2.26 0 0 0 9.454 16a2.26 2.26 0 0 0-2.27-2.25A2.26 2.26 0 0 0 4.917 16a2.26 2.26 0 0 0 2.269 2.25Zm17.647-6.501a1.38 1.38 0 0 0 1.386-1.375A1.38 1.38 0 0 0 24.832 9a1.38 1.38 0 0 0-1.386 1.374c0 .76.621 1.375 1.386 1.375ZM15 6.25a1.38 1.38 0 0 0 1.385-1.375c0-.76-.62-1.375-1.386-1.375a1.38 1.38 0 0 0-1.386 1.375c0 .759.62 1.374 1.386 1.374Zm-9.833 5.499a1.38 1.38 0 0 0 1.386-1.375A1.38 1.38 0 0 0 5.167 9a1.38 1.38 0 0 0-1.386 1.374c0 .76.621 1.375 1.386 1.375Zm0 11.251a1.38 1.38 0 0 0 1.386-1.374c0-.76-.62-1.375-1.386-1.375a1.38 1.38 0 0 0-1.386 1.375A1.38 1.38 0 0 0 5.167 23ZM15 28.625a1.38 1.38 0 0 0 1.385-1.374c0-.76-.62-1.375-1.386-1.375a1.38 1.38 0 0 0-1.386 1.375c0 .759.62 1.374 1.386 1.374ZM24.832 23a1.38 1.38 0 0 0 1.386-1.374c0-.76-.62-1.375-1.386-1.375a1.38 1.38 0 0 0-1.386 1.375A1.38 1.38 0 0 0 24.832 23ZM22.059 4.751a.88.88 0 0 0 .883-.875.88.88 0 0 0-.883-.876.88.88 0 0 0-.883.876.88.88 0 0 0 .883.875Zm-14.118 0a.88.88 0 0 0 .883-.875A.88.88 0 0 0 7.94 3a.88.88 0 0 0-.883.876.88.88 0 0 0 .883.875ZM.883 16.876a.88.88 0 0 0 .883-.875.88.88 0 0 0-.883-.876.88.88 0 0 0-.883.876.88.88 0 0 0 .883.875ZM7.941 29a.88.88 0 0 0 .883-.875.88.88 0 0 0-.883-.876.88.88 0 0 0-.883.876.88.88 0 0 0 .883.875Zm14.118 0a.88.88 0 0 0 .883-.875.88.88 0 0 0-.883-.876.88.88 0 0 0-.883.876.88.88 0 0 0 .883.875Zm7.058-12.124a.88.88 0 0 0 .883-.875.88.88 0 0 0-.883-.876.88.88 0 0 0-.883.876.88.88 0 0 0 .883.875Zm-.503-8.251a.377.377 0 0 0 .378-.375.377.377 0 0 0-.378-.375.377.377 0 0 0-.378.375c0 .207.17.375.378.375ZM15 .75a.377.377 0 0 0 .377-.375A.377.377 0 0 0 15 0a.377.377 0 0 0-.378.375c0 .207.17.375.378.375ZM1.386 8.625a.377.377 0 0 0 .378-.375.377.377 0 0 0-.378-.375.377.377 0 0 0-.378.375c0 .207.17.375.378.375Zm0 15.625a.377.377 0 0 0 .378-.374.377.377 0 0 0-.378-.375.377.377 0 0 0-.378.375c0 .207.17.375.378.375Zm27.228-.125a.377.377 0 0 0 .378-.375.377.377 0 0 0-.378-.375.377.377 0 0 0-.378.375c0 .207.17.375.378.375Z"></path><path d="M19.538 20.5c-1.007-.374-2.142.126-2.646 1-.505.876-1.513 1.375-2.647 1-.63-.126-1.008-.625-1.261-1 0-.125-.127-.25-.127-.5 0-1 .756-1.75 1.64-1.875h.25c1.765.125 3.277-1.375 3.404-3.126.127-1.75-1.386-3.25-3.152-3.375h-.378c-1.008 0-1.64-.75-1.64-1.75 0-.249 0-.5.127-.624v-.125c0-.126.127-.126.127-.25.378-1.25-.252-2.5-1.512-2.874-1.135-.375-2.52.25-2.9 1.5-.377 1.125.252 2.375 1.387 2.874.168.084.336.126.505.126h.126c.883.125 1.64.875 1.64 1.75 0 .374-.127.624-.252.875-.252.5-.505 1-.505 1.625 0 .626.127 1.375.505 1.875.126.25.251.625.251.876 0 .875-.63 1.625-1.512 1.75h-.378c-1.261.249-2.018 1.5-1.764 2.75.253 1.25 1.512 2 2.646 1.75.63-.126 1.135-.501 1.386-1 .505-.876 1.64-1.375 2.647-1 .505.126 1.008.5 1.262 1 .251.375.756.75 1.26 1 1.262.374 2.396-.25 2.9-1.5.504-1.126-.127-2.376-1.387-2.751h-.002Z"></path></g>
</svg>

+ 191
- 0
MindpilotUI-1/src/renderer/src/components/ListCard.vue View File

@@ -0,0 +1,191 @@
<template>
<div :class="cardClass">
<div class="list-card-item_detail bg-bg_color">
<div class="list-card-item_header">
<div class="list-card-item_detail--left">
<div :class="cardLogoClass">
<Icon icon="fluent-emoji:open-book"></Icon>
</div>
<p class="list-card-item_detail--name text-text_color_primary">
{{ product.name }}
</p>
</div>
<div class="list-card-item_detail--operation">
<el-dropdown trigger="click" :disabled="!product.isSetup">
<IconifyIconOffline :icon="More2Fill" class="text-[24px]" />
<template #dropdown>
<el-dropdown-menu :disabled="!product.isSetup">
<el-dropdown-item @click="handleClickManage(product)"> 管理 </el-dropdown-item>
<el-dropdown-item @click="handleClickDelete(product)"> 删除 </el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<p class="list-card-item_detail--desc text-text_color_regular">
{{ product.description }}
</p>
<el-row class="mt-4">
<el-col :span="12">
<el-statistic title="文件数量" :value="formattedFileCount" />
</el-col>
<el-col :span="12">
<el-statistic title="最后更新时间" :value="formattedLastUpdatedAt" />
</el-col>
</el-row>
</div>
</div>
</template>

<script setup lang="ts">
import { computed, PropType } from 'vue'
import { Icon } from '@iconify/vue'
import More2Fill from '@iconify-icons/ri/more-2-fill'
import IconifyIconOffline from './ReIcon/src/iconifyIconOffline'

defineOptions({
name: 'ReCard'
})

interface CardProductType {
type: number
isSetup: boolean
description: string
name: string
fileCount: number
lastUpdatedAt: Date | null
}

const props = defineProps({
product: {
type: Object as PropType<CardProductType>,
required: true
}
})

const emit = defineEmits(['manage-product', 'delete-item'])

const handleClickManage = (product: CardProductType) => {
emit('manage-product', product)
}

const handleClickDelete = (product: CardProductType) => {
emit('delete-item', product)
}

const cardClass = computed(() => [
'list-card-item',
{ 'list-card-item__disabled': !props.product.isSetup }
])

const cardLogoClass = computed(() => [
'list-card-item_detail--logo',
{ 'list-card-item_detail--logo__disabled': !props.product.isSetup }
])

const formattedFileCount = computed(() => props.product.fileCount ?? 0)

const formattedLastUpdatedAt = computed(() => {
const date = new Date(props.product.lastUpdatedAt)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
})
</script>

<style scoped lang="scss">
.list-card-item {
display: flex;
flex-direction: column;
margin-bottom: 12px;
overflow: hidden;
cursor: pointer;
border-radius: 3px;

&_detail {
flex: 1;
min-height: 140px;
padding: 24px 32px;

&--left {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
overflow: hidden;
}

&--logo {
display: flex;
align-items: center;
justify-content: center;
width: 46px;
height: 46px;
font-size: 26px;
color: #0052d9;
background: #e0ebff;
border-radius: 50%;
margin-right: 16px;
flex-shrink: 0;

&__disabled {
color: #a1c4ff;
}
}

&--operation {
display: flex;
align-items: center;

&--tag {
border: 0;
margin-right: 8px;
}
}

&--name {
margin: 0;
font-size: 16px;
font-weight: 400;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

&--desc {
display: -webkit-box;
height: 40px;
margin-top: 14px;
margin-bottom: 0;
overflow: hidden;
font-size: 14px;
line-height: 20px;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}

&_header {
display: flex;
align-items: center;
justify-content: space-between;
}

&__disabled {
.list-card-item_detail--name,
.list-card-item_detail--desc {
color: var(--el-text-color-disabled);
}

.list-card-item_detail--operation--tag {
color: #bababa;
}
}
}
</style>

+ 3869
- 0
MindpilotUI-1/src/renderer/src/components/ReIcon/data.ts
File diff suppressed because it is too large
View File


+ 15
- 0
MindpilotUI-1/src/renderer/src/components/ReIcon/index.ts View File

@@ -0,0 +1,15 @@
import iconifyIconOffline from "./src/iconifyIconOffline";
import iconifyIconOnline from "./src/iconifyIconOnline";
import iconSelect from "./src/Select.vue";
import fontIcon from "./src/iconfont";

/** 本地图标组件 */
const IconifyIconOffline = iconifyIconOffline;
/** 在线图标组件 */
const IconifyIconOnline = iconifyIconOnline;
/** `IconSelect`图标选择器组件 */
const IconSelect = iconSelect;
/** `iconfont`组件 */
const FontIcon = fontIcon;

export { IconifyIconOffline, IconifyIconOnline, IconSelect, FontIcon };

+ 270
- 0
MindpilotUI-1/src/renderer/src/components/ReIcon/src/Select.vue View File

@@ -0,0 +1,270 @@
<script setup lang="ts">
import { IconJson } from "../data";
import { cloneDeep, isAllEmpty } from "@pureadmin/utils";
import { ref, computed, CSSProperties, watch } from "vue";
import Search from "@iconify-icons/ri/search-eye-line";
import IconifyIconOffline from "./iconifyIconOffline";
import IconifyIconOnline from "./iconifyIconOnline";

type ParameterCSSProperties = (item?: string) => CSSProperties | undefined;

defineOptions({
name: "IconSelect"
});

const inputValue = defineModel({ type: String });

const iconList = ref(IconJson);
const icon = ref();
const currentActiveType = ref("ep:");
// 深拷贝图标数据,前端做搜索
const copyIconList = cloneDeep(iconList.value);
const totalPage = ref(0);
// 每页显示35个图标
const pageSize = ref(35);
const currentPage = ref(1);

// 搜索条件
const filterValue = ref("");

const tabsList = [
{
label: "Element Plus",
name: "ep:"
},
{
label: "Remix Icon",
name: "ri:"
},
{
label: "Font Awesome 5 Solid",
name: "fa-solid:"
}
];

const pageList = computed(() =>
copyIconList[currentActiveType.value]
.filter(i => i.includes(filterValue.value))
.slice(
(currentPage.value - 1) * pageSize.value,
currentPage.value * pageSize.value
)
);

const iconItemStyle = computed((): ParameterCSSProperties => {
return item => {
if (inputValue.value === currentActiveType.value + item) {
return {
borderColor: "var(--el-color-primary)",
color: "var(--el-color-primary)"
};
}
};
});

function setVal() {
currentActiveType.value = inputValue.value.substring(
0,
inputValue.value.indexOf(":") + 1
);
icon.value = inputValue.value.substring(inputValue.value.indexOf(":") + 1);
}

function onBeforeEnter() {
if (isAllEmpty(icon.value)) return;
setVal();
// 寻找当前图标在第几页
const curIconIndex = copyIconList[currentActiveType.value].findIndex(
i => i === icon.value
);
currentPage.value = Math.ceil((curIconIndex + 1) / pageSize.value);
}

function onAfterLeave() {
filterValue.value = "";
}

function handleClick({ props }) {
currentPage.value = 1;
currentActiveType.value = props.name;
}

function onChangeIcon(item) {
icon.value = item;
inputValue.value = currentActiveType.value + item;
}

function onCurrentChange(page) {
currentPage.value = page;
}

function onClear() {
icon.value = "";
inputValue.value = "";
}

watch(
() => pageList.value,
() =>
(totalPage.value = copyIconList[currentActiveType.value].filter(i =>
i.includes(filterValue.value)
).length),
{ immediate: true }
);
watch(
() => inputValue.value,
val => val && setVal(),
{ immediate: true }
);
watch(
() => filterValue.value,
() => (currentPage.value = 1)
);
</script>

<template>
<div class="selector">
<el-input v-model="inputValue" disabled>
<template #append>
<el-popover
:width="350"
trigger="click"
popper-class="pure-popper"
:popper-options="{
placement: 'auto'
}"
@before-enter="onBeforeEnter"
@after-leave="onAfterLeave"
>
<template #reference>
<div
class="w-[40px] h-[32px] cursor-pointer flex justify-center items-center"
>
<IconifyIconOffline v-if="!icon" :icon="Search" />
<IconifyIconOnline v-else :icon="inputValue" />
</div>
</template>

<el-input
v-model="filterValue"
class="px-2 pt-2"
placeholder="搜索图标"
clearable
/>

<el-tabs v-model="currentActiveType" @tab-click="handleClick">
<el-tab-pane
v-for="(pane, index) in tabsList"
:key="index"
:label="pane.label"
:name="pane.name"
>
<el-scrollbar height="220px">
<ul class="flex flex-wrap px-2 ml-2">
<li
v-for="(item, key) in pageList"
:key="key"
:title="item"
class="icon-item p-2 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-[#e5e7eb]"
:style="iconItemStyle(item)"
@click="onChangeIcon(item)"
>
<IconifyIconOnline
:icon="currentActiveType + item"
width="20px"
height="20px"
/>
</li>
</ul>
<el-empty
v-show="pageList.length === 0"
:description="`${filterValue} 图标不存在`"
:image-size="60"
/>
</el-scrollbar>
</el-tab-pane>
</el-tabs>

<div
class="w-full h-9 flex items-center overflow-auto border-t border-[#e5e7eb]"
>
<el-pagination
class="flex-auto ml-2"
:total="totalPage"
:current-page="currentPage"
:page-size="pageSize"
:pager-count="5"
layout="pager"
background
small
@current-change="onCurrentChange"
/>
<el-button
class="justify-end mr-2 ml-2"
type="danger"
size="small"
text
bg
@click="onClear"
>
清空
</el-button>
</div>
</el-popover>
</template>
</el-input>
</div>
</template>

<style lang="scss" scoped>
.icon-item {
&:hover {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
transition: all 0.4s;
transform: scaleX(1.05);
}
}

:deep(.el-tabs__nav-next) {
font-size: 15px;
line-height: 32px;
box-shadow: -5px 0 5px -6px #ccc;
}

:deep(.el-tabs__nav-prev) {
font-size: 15px;
line-height: 32px;
box-shadow: 5px 0 5px -6px #ccc;
}

:deep(.el-input-group__append) {
padding: 0;
}

:deep(.el-tabs__item) {
height: 30px;
font-size: 12px;
font-weight: normal;
line-height: 30px;
}

:deep(.el-tabs__header),
:deep(.el-tabs__nav-wrap) {
position: static;
margin: 0;
box-shadow: 0 2px 5px rgb(0 0 0 / 6%);
}

:deep(.el-tabs__nav-wrap::after) {
height: 0;
}

:deep(.el-tabs__nav-wrap) {
padding: 0 24px;
}

:deep(.el-tabs__content) {
margin-top: 4px;
}
</style>

+ 63
- 0
MindpilotUI-1/src/renderer/src/components/ReIcon/src/hooks.ts View File

@@ -0,0 +1,63 @@
import type { iconType } from "./types";
import { h, defineComponent, type Component } from "vue";
import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index";

/**
* 支持 `iconfont`、自定义 `svg` 以及 `iconify` 中所有的图标
* @see 点击查看文档图标篇 {@link https://pure-admin.github.io/pure-admin-doc/pages/icon/}
* @param icon 必传 图标
* @param attrs 可选 iconType 属性
* @returns Component
*/
export function useRenderIcon(icon: any, attrs?: iconType): Component { // eslint-disable-line @typescript-eslint/no-explicit-any
// iconfont
const ifReg = /^IF-/;
// typeof icon === "function" 属于SVG
if (ifReg.test(icon)) {
// iconfont
const name = icon.split(ifReg)[1];
const iconName = name.slice(
0,
name.indexOf(" ") == -1 ? name.length : name.indexOf(" ")
);
const iconType = name.slice(name.indexOf(" ") + 1, name.length);
return defineComponent({
name: "FontIcon",
render() {
return h(FontIcon, {
icon: iconName,
iconType,
...attrs
});
}
});
} else if (typeof icon === "function" || typeof icon?.render === "function") {
// svg
return attrs ? h(icon, { ...attrs }) : icon;
} else if (typeof icon === "object") {
return defineComponent({
name: "OfflineIcon",
render() {
return h(IconifyIconOffline, {
icon: icon,
...attrs
});
}
});
} else {
// 通过是否存在 : 符号来判断是在线还是本地图标,存在即是在线图标,反之
return defineComponent({
name: "Icon",
render() {
const IconifyIcon =
icon && icon.includes(":") ? IconifyIconOnline : IconifyIconOffline;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return h(IconifyIcon, {
icon: icon,
...attrs
});
}
});
}
}

+ 48
- 0
MindpilotUI-1/src/renderer/src/components/ReIcon/src/iconfont.ts View File

@@ -0,0 +1,48 @@
import { h, defineComponent } from "vue";

// 封装iconfont组件,默认`font-class`引用模式,支持`unicode`引用、`font-class`引用、`symbol`引用 (https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.20&helptype=code)
export default defineComponent({
name: "FontIcon",
props: {
icon: {
type: String,
default: ""
}
},
render() {
const attrs = this.$attrs;
if (Object.keys(attrs).includes("uni") || attrs?.iconType === "uni") {
return h(
"i",
{
class: "iconfont",
...attrs
},
this.icon
);
} else if (
Object.keys(attrs).includes("svg") ||
attrs?.iconType === "svg"
) {
return h(
"svg",
{
class: "icon-svg",
"aria-hidden": true
},
{
default: () => [
h("use", {
"xlink:href": `#${this.icon}`
})
]
}
);
} else {
return h("i", {
class: `iconfont ${this.icon}`,
...attrs
});
}
}
});

+ 33
- 0
MindpilotUI-1/src/renderer/src/components/ReIcon/src/iconifyIconOffline.ts View File

@@ -0,0 +1,33 @@
import { h, defineComponent, PropType } from 'vue'
import { Icon as IconifyIcon, addIcon } from '@iconify/vue/dist/offline'

// Iconify Icon在Vue里本地使用(用于内网环境)
export default defineComponent({
name: 'IconifyIconOffline',
components: { IconifyIcon },
props: {
icon: {
type: [Object, null] as PropType<IconifyIcon | null>,
default: null
}
},
render() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
if (typeof this.icon === 'object') addIcon(this.icon, this.icon)
const attrs = this.$attrs
return h(
IconifyIcon,
{
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
icon: this.icon,
style: attrs?.style ? Object.assign(attrs.style, { outline: 'none' }) : { outline: 'none' },
...attrs
},
{
default: () => []
}
)
}
})

+ 30
- 0
MindpilotUI-1/src/renderer/src/components/ReIcon/src/iconifyIconOnline.ts View File

@@ -0,0 +1,30 @@
import { h, defineComponent } from "vue";
import { Icon as IconifyIcon } from "@iconify/vue";

// Iconify Icon在Vue里在线使用(用于外网环境)
export default defineComponent({
name: "IconifyIconOnline",
components: { IconifyIcon },
props: {
icon: {
type: String,
default: ""
}
},
render() {
const attrs = this.$attrs;
return h(
IconifyIcon,
{
icon: `${this.icon}`,
style: attrs?.style
? Object.assign(attrs.style, { outline: "none" })
: { outline: "none" },
...attrs
},
{
default: () => []
}
);
}
});

+ 70
- 0
MindpilotUI-1/src/renderer/src/components/ReIcon/src/offlineIcon.ts View File

@@ -0,0 +1,70 @@
// 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载
import { addIcon } from "@iconify/vue/dist/offline";

// 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标
// @iconify-icons/ep
import Menu from "@iconify-icons/ep/menu";
import Edit from "@iconify-icons/ep/edit";
import SetUp from "@iconify-icons/ep/set-up";
import Guide from "@iconify-icons/ep/guide";
import Monitor from "@iconify-icons/ep/monitor";
import Lollipop from "@iconify-icons/ep/lollipop";
import Histogram from "@iconify-icons/ep/histogram";
import HomeFilled from "@iconify-icons/ep/home-filled";
addIcon("ep:menu", Menu);
addIcon("ep:edit", Edit);
addIcon("ep:set-up", SetUp);
addIcon("ep:guide", Guide);
addIcon("ep:monitor", Monitor);
addIcon("ep:lollipop", Lollipop);
addIcon("ep:histogram", Histogram);
addIcon("ep:home-filled", HomeFilled);
// @iconify-icons/ri
import Tag from "@iconify-icons/ri/bookmark-2-line";
import Ppt from "@iconify-icons/ri/file-ppt-2-line";
import Card from "@iconify-icons/ri/bank-card-line";
import Role from "@iconify-icons/ri/admin-fill";
import Info from "@iconify-icons/ri/file-info-line";
import Dept from "@iconify-icons/ri/git-branch-line";
import Table from "@iconify-icons/ri/table-line";
import Links from "@iconify-icons/ri/links-fill";
import Search from "@iconify-icons/ri/search-line";
import FlUser from "@iconify-icons/ri/admin-line";
import Setting from "@iconify-icons/ri/settings-3-line";
import MindMap from "@iconify-icons/ri/mind-map";
import BarChart from "@iconify-icons/ri/bar-chart-horizontal-line";
import LoginLog from "@iconify-icons/ri/window-line";
import Artboard from "@iconify-icons/ri/artboard-line";
import SystemLog from "@iconify-icons/ri/file-search-line";
import ListCheck from "@iconify-icons/ri/list-check";
import UbuntuFill from "@iconify-icons/ri/ubuntu-fill";
import OnlineUser from "@iconify-icons/ri/user-voice-line";
import EditBoxLine from "@iconify-icons/ri/edit-box-line";
import OperationLog from "@iconify-icons/ri/history-fill";
import InformationLine from "@iconify-icons/ri/information-line";
import TerminalWindowLine from "@iconify-icons/ri/terminal-window-line";
import CheckboxCircleLine from "@iconify-icons/ri/checkbox-circle-line";
addIcon("ri:bookmark-2-line", Tag);
addIcon("ri:file-ppt-2-line", Ppt);
addIcon("ri:bank-card-line", Card);
addIcon("ri:admin-fill", Role);
addIcon("ri:file-info-line", Info);
addIcon("ri:git-branch-line", Dept);
addIcon("ri:links-fill", Links);
addIcon("ri:table-line", Table);
addIcon("ri:search-line", Search);
addIcon("ri:admin-line", FlUser);
addIcon("ri:settings-3-line", Setting);
addIcon("ri:mind-map", MindMap);
addIcon("ri:bar-chart-horizontal-line", BarChart);
addIcon("ri:window-line", LoginLog);
addIcon("ri:file-search-line", SystemLog);
addIcon("ri:artboard-line", Artboard);
addIcon("ri:list-check", ListCheck);
addIcon("ri:ubuntu-fill", UbuntuFill);
addIcon("ri:user-voice-line", OnlineUser);
addIcon("ri:edit-box-line", EditBoxLine);
addIcon("ri:history-fill", OperationLog);
addIcon("ri:information-line", InformationLine);
addIcon("ri:terminal-window-line", TerminalWindowLine);
addIcon("ri:checkbox-circle-line", CheckboxCircleLine);

+ 20
- 0
MindpilotUI-1/src/renderer/src/components/ReIcon/src/types.ts View File

@@ -0,0 +1,20 @@
export interface iconType {
// iconify (https://docs.iconify.design/icon-components/vue/#properties)
inline?: boolean;
width?: string | number;
height?: string | number;
horizontalFlip?: boolean;
verticalFlip?: boolean;
flip?: string;
rotate?: number | string;
color?: string;
horizontalAlign?: boolean;
verticalAlign?: boolean;
align?: string;
onLoad?: Function;
includes?: Function;
// svg 需要什么SVG属性自行添加
fill?: string;
// all icon
style?: object;
}

+ 7
- 0
MindpilotUI-1/src/renderer/src/components/ReText/index.ts View File

@@ -0,0 +1,7 @@
import reText from "./src/index.vue";
import { withInstall } from "@pureadmin/utils";

/** 支持`Tooltip`提示的文本省略组件 */
export const ReText = withInstall(reText);

export default ReText;

+ 68
- 0
MindpilotUI-1/src/renderer/src/components/ReText/src/index.vue View File

@@ -0,0 +1,68 @@
<script lang="ts" setup>
import { h, onMounted, PropType, ref, useSlots } from 'vue'
import { type TippyOptions, useTippy } from "vue-tippy";

defineOptions({
agent_name: "ReText"
});

const props = defineProps({
// 行数
lineClamp: {
type: [String, Number]
},
tippyProps: {
type: Object as PropType<TippyOptions>,
default: () => ({})
}
});

const $slots = useSlots();

const textRef = ref();
const tippyFunc = ref();

const isTextEllipsis = (el: HTMLElement) => {
if (!props.lineClamp) {
// 单行省略判断
return el.scrollWidth > el.clientWidth;
} else {
// 多行省略判断
return el.scrollHeight > el.clientHeight;
}
};

const getTippyProps = () => ({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
content: h($slots.content || $slots.default),
...props.tippyProps
});

function handleHover(event: MouseEvent) {
if (isTextEllipsis(event.target as HTMLElement)) {
tippyFunc.value.setProps(getTippyProps());
tippyFunc.value.enable();
} else {
tippyFunc.value.disable();
}
}

onMounted(() => {
tippyFunc.value = useTippy(textRef.value?.$el, getTippyProps());
});
</script>

<template>
<el-text
v-bind="{
truncated: !lineClamp,
lineClamp,
...$attrs
}"
ref="textRef"
@mouseover.self="handleHover"
>
<slot />
</el-text>
</template>

+ 13
- 0
MindpilotUI-1/src/renderer/src/components/Versions.vue View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
import { reactive } from 'vue'

const versions = reactive({ ...window.electron.process.versions })
</script>

<template>
<ul class="versions">
<li class="electron-version">Electron v{{ versions.electron }}</li>
<li class="chrome-version">Chromium v{{ versions.chrome }}</li>
<li class="node-version">Node v{{ versions.node }}</li>
</ul>
</template>

+ 123
- 0
MindpilotUI-1/src/renderer/src/components/fileCard.vue View File

@@ -0,0 +1,123 @@
<template>
<el-card shadow="hover" class="file-card">
<el-row :gutter="20" align="middle" justify="space-between">
<el-col :span="20">
<div class="d-flex align-items-center">
<div class="file-icon mr-2">
<Icon :icon="fileIcon" :width="24" :height="24" />
</div>
<el-tooltip :content="fileName" placement="top" :show-after="1000">
<span class="file-name">{{ fileName }}</span>
</el-tooltip>
</div>
</el-col>
<el-col :span="4" class="text-right">
<el-popconfirm
title="确定要删除这个文件吗?"
confirm-button-text="确定"
cancel-button-text="取消"
@confirm="handleDelete"
>
<template #reference>
<el-button type="danger" :icon="Delete" circle size="small" />
</template>
</el-popconfirm>
</el-col>
</el-row>
</el-card>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { Delete } from '@element-plus/icons-vue'
import { Icon } from '@iconify/vue'

defineOptions({
name: 'ReFileCard'
})

interface Props {
fileName: string
lastUpdated: string
knowledgeBaseName: string
}

const props = defineProps<Props>()
const emit = defineEmits(['delete'])

const fileExtension = computed(() => {
const parts = props.fileName.split('.')
return parts.length > 1 ? parts.pop()!.toLowerCase() : ''
})

const fileIcon = computed(() => {
switch (fileExtension.value) {
case 'md':
return 'vscode-icons:file-type-markdown'
case 'js':
return 'vscode-icons:file-type-js'
case 'ts':
return 'vscode-icons:file-type-typescript'
case 'html':
return 'vscode-icons:file-type-html'
case 'css':
return 'vscode-icons:file-type-css'
case 'json':
return 'vscode-icons:file-type-json'
case 'py':
return 'vscode-icons:file-type-python'
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
return 'vscode-icons:file-type-image'
case 'pdf':
return 'vscode-icons:file-type-pdf'
// 添加更多文件类型...
default:
return 'vscode-icons:default-file'
}
})

const handleDelete = () => {
emit('delete', props.knowledgeBaseName, props.fileName)
}
</script>

<style scoped>
.file-card {
margin-bottom: 10px;
}

.file-name {
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
display: inline-block;
vertical-align: middle;
}

.d-flex {
display: flex;
}

.align-items-center {
align-items: center;
}

.mr-2 {
margin-right: 15px;
}

.text-right {
text-align: right;
}

.file-icon {
display: flex;
align-items: center;
justify-content: center;
}
</style>

+ 15
- 0
MindpilotUI-1/src/renderer/src/env.d.ts View File

@@ -0,0 +1,15 @@
/// <reference types="vite/client" />

declare module "*.vue" {
import type { DefineComponent } from "vue";
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
}

declare module "*.svg?component" {
import { DefineComponent } from "vue";
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
}

+ 21
- 0
MindpilotUI-1/src/renderer/src/main.ts View File

@@ -0,0 +1,21 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import { setupRouter } from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import contextmenu from 'v-contextmenu'
import 'v-contextmenu/dist/themes/default.css'

const app = createApp(App)
const pinia = createPinia()

app.use(contextmenu)
app.use(pinia)
app.use(ElementPlus)
setupRouter(app)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.mount('#app')

+ 32
- 0
MindpilotUI-1/src/renderer/src/router/index.ts View File

@@ -0,0 +1,32 @@
import type { App } from 'vue'
import type { RouteRecordRaw } from 'vue-router'
import { createRouter, createWebHashHistory } from 'vue-router'

const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'Home',
component: () => import('@renderer/views/home.vue')
},
{
path: '/agentconfig',
name: 'AgentConfig',
component: () => import('@renderer/views/agentconfig.vue')
},
{
path: '/kbconfig',
name: 'kbconfig',
component: () => import('@renderer/views/knowledgebase/kbconfig.vue')
}
]

export const router = createRouter({
history: createWebHashHistory(),
routes,
scrollBehavior: () => ({ left: 0, top: 0 })
})

export async function setupRouter(app: App) {
app.use(router)
await router.isReady()
}

+ 28
- 0
MindpilotUI-1/src/renderer/src/store/store.ts View File

@@ -0,0 +1,28 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { Conversation } from '../views/conversationApi'

export const useModelConfigStore = defineStore('modelConfigStore', () => {
const config_id_cache = ref('')

const setConfigId = (configId: string) => {
config_id_cache.value = configId
}

const getConfigId = () => {
return config_id_cache.value
}

return { config_id_cache, setConfigId, getConfigId }
})
export const useConversationStore = defineStore('conversationStore', () => {
const currentConversation = ref<Conversation | null>(null)

const setCurrentConversation = (conversation: Conversation | null) => {
currentConversation.value = conversation
}
const getCurrentConversation = () => {
return currentConversation.value
}
return { currentConversation, setCurrentConversation, getCurrentConversation }
})

+ 10
- 0
MindpilotUI-1/src/renderer/src/utils/tools.ts View File

@@ -0,0 +1,10 @@
function generateFourDigitNumber(): string {
const min = 1000;
const max = 9999;
const randomNumber = Math.floor(Math.random() * (max - min + 1)) + min;
return randomNumber.toString();
}

export const generateAssistantWithRandomID = () => {
return "assistant" + generateFourDigitNumber();
};

+ 686
- 0
MindpilotUI-1/src/renderer/src/views/agentconfig.vue View File

@@ -0,0 +1,686 @@
<template>
<div class="agentconfig-layout">
<el-container class="full-height">
<el-header class="header">
<div class="left-icons">
<el-button class="back-button" type="text" @click="goBack">
<Icon
icon="weui:back-filled"
width="40"
height="40"
style="color: #4d4d4d"
class="back-icon"
/>
<!-- <img :src="backButton" alt="Back" class="back-icon" />-->
</el-button>
<el-avatar :size="50" :src="avatarIcon" />
</div>
<div class="config-sub-menu horizontal-menu" style="left: 50%; position: absolute">
<el-dropdown @command="selectDebugModel">
<span class="el-dropdown-link">
{{ debugConfigSettings.config_name || '未选择配置' }}
<el-icon class="el-icon--right"><arrow-down /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="config in configs"
:key="config.config_id"
:command="config"
>
{{ config.config_name }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="right-buttons">
<el-button
class="config-button delete-config-button"
type="primary"
plain
@click="deleteConfig"
>删除
</el-button>
<el-button
class="config-button save-config-button"
type="danger"
plain
@click="saveConfig"
>发布
</el-button>
</div>
</el-header>
<el-container class="content-container">
<div style="width: 50%" class="config-menu">
<div class="config-title">配置智能体</div>
<el-form :model="agentForm" label-position="top" class="config-form">
<el-form-item label="图标">
<el-avatar class="agent-avatar" :size="50" :src="avatarIcon" @click="onUploadIcon" />
<div class="el-upload__tip">&nbsp;&nbsp; 只能上传jpg/png文件,且不超过 1 mb</div>
</el-form-item>
<el-form-item label="名称">
<el-input v-model="agentForm.agent_name" placeholder="命名你的工具"></el-input>
</el-form-item>
<el-form-item label="简介">
<el-input
v-model="agentForm.agent_abstract"
type="textarea"
placeholder="一句话介绍你的工具"
></el-input>
</el-form-item>
<el-form-item label="配置信息">
<el-input
v-model="agentForm.agent_info"
type="textarea"
:placeholder="configPlaceHolder"
rows="4"
></el-input>
</el-form-item>
<el-form-item label="能力配置">
<el-select v-model="selectedCapabilities" multiple placeholder="选择能力">
<el-option
v-for="item in capabilities"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="自定义能力">
<div
style="
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
"
>
<span class="config-tips">让智能体调用外部AP来实现复杂功能</span>
<el-button plain @click="createTool">自建插件</el-button>
</div>
</el-form-item>
<el-form-item label="温度">
<span class="temperature-tips config-tips"
>温度越高,回答越随机,温度越低,回答越固定</span
>
<el-slider
v-model="temperatureValue"
:min="0.1"
:max="1"
:step="0.1"
show-input
></el-slider>
</el-form-item>
<el-form-item label="最大Token">
<div
style="
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
"
>
<span class="config-tips">模型最大输出长度</span>
<el-input-number
v-model="agentForm.max_tokens"
:min="1"
placeholder="输入最大长度"
></el-input-number>
</div>
</el-form-item>
</el-form>
</div>
<div
class="preview-container"
:class="{ disabled: isPreviewDisabled }"
@mouseover="showTooltip = true"
@mouseleave="showTooltip = false"
>
<span class="config-title">调试与预览</span>
<div class="preview-area" :class="{ 'cursor-not-allowed': isPreviewDisabled }">
<deep-chat
id="chat-element"
avatars="true"
:text-input="{
placeholder: { text: '请输入你的问题...' }
}"
:speech-to-text="{
button: {
default: {
container: {
default: {
bottom: '1em',
right: '0.6em',
borderRadius: '20px',
width: '1.9em',
height: '1.9em'
}
},
svg: { styles: { default: { bottom: '0.35em', left: '0.35em' } } }
},
position: 'inside-right'
}
}"
:mixed-files="{ button: { position: 'inside-left' } }"
:demo="true"
style="width: 100%; height: 100%; background-color: #ffffff; border: none"
>
</deep-chat>
</div>
<el-tooltip
:content="tooltipContent"
placement="top"
:disabled="!isPreviewDisabled || !showTooltip"
effect="light"
popper-class="custom-tooltip"
>
<div v-if="isPreviewDisabled" class="overlay"></div>
</el-tooltip>
</div>
</el-container>
</el-container>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import 'deep-chat'
import {
ElForm,
ElFormItem,
ElInput,
ElButton,
ElUpload,
ElContainer,
ElHeader,
ElSlider,
ElSelect,
ElOption,
ElMessage
} from 'element-plus'
import { ArrowDown, UploadFilled } from '@element-plus/icons-vue'
import uploadIcon from '../assets/material-symbols--upload-sharp.png'
import { Icon } from '@iconify/vue'
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios'
import type { DeepChat } from 'deep-chat'

import { Signals } from 'deep-chat/dist/types/handler'
import { ModelConfig, useConfigManagement } from './configManagement'

let chatElementRef: DeepChat | null = null
const capabilities = ref<Tool[]>([])
const avatarIcon = ref(uploadIcon)
const selectedCapabilities = ref([])
const temperatureValue = ref(1)
const agentForm = reactive({
agent_name: '',
agent_abstract: '',
agent_info: '',
max_tokens: 4096
})

const fakeRAGOptions = ref([
{
value: 'knowledge_base',
label: '知识库1'
},
{
value: 'knowledge_base1',
label: '知识库2'
}
])
const selectedRAGOption = ref('')

const onUploadIcon = () => {
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/jpeg, image/png'
input.onchange = (event) => {
const file = (event.target as HTMLInputElement).files?.[0]
if (file) {
const reader = new FileReader()
reader.onload = (e) => {
avatarIcon.value = e.target?.result as string
}
reader.readAsDataURL(file)
}
}
input.click()
}

const configPlaceHolder =
'' +
'请详细描述你的工具设定,例如:\n' +
' 工具特点,说明ta的能力、希望ta完成的工作或目标,ta的作用\n' +
' 工具身份,描述ta的角色、和用户交互形式,需要规避的异常行为\n' +
' 工具行为,指定ta的行为特点、性格或个性化回复用户的方式\n'
const router = useRouter() // 获取router实例
const goBack = () => {
router.push('/')
}

onMounted(() => {
getAvailableTools()

const chatElement = document.querySelector('#chat-element')
if (chatElement?.shadowRoot) {
const observer = new MutationObserver((_, obs) => {
const intropanel = chatElement.shadowRoot?.querySelector(
'#messages > div.intro-panel'
) as HTMLElement | null
if (intropanel) {
intropanel.style.display = 'flex'
obs.disconnect() // 停止观察
}
})

observer.observe(chatElement.shadowRoot, {
childList: true, // 观察直接子节点的添加或删除
subtree: true, // 观察所有后代节点
attributes: false, // 不观察属性变化
characterData: false // 不观察文本内容变化
})

// 清理函数
onUnmounted(() => {
observer.disconnect()
})
}
})

const createTool = () => {
// 创建工具的逻辑
}

const getAvailableTools = async () => {
try {
const response = await axios.get('http://127.0.0.1:7861/api/tools/available_tools', {
headers: {
accept: 'application/json'
}
})
console.log(response.data)
const tools = response.data.tools
capabilities.value = tools.map((tool: string) => ({ label: tool, value: tool }))
} catch (error) {
console.error('Error fetching available tools:', error)
}
}

const deleteConfig = async () => {
try {
const delete_agent_id = Number.parseInt(agentId.value as string, 10)
const response = await axios.delete('http://127.0.0.1:7861/api/agent/delete_agent', {
headers: {
accept: 'application/json',
'Content-Type': 'application/json'
},
data: delete_agent_id
})
console.log(response)
ElMessage.success({
message: '成功删除Agent配置',
type: 'success'
})
// 处理成功响应
} catch (error) {
console.error('Error deleting agent:', error)
ElMessage.error('删除配置失败')
}
}

//监视agentForm.agent_name 如果有变化,就更新introMessage
watch(
() => agentForm.agent_name,
(newValue, oldValue) => {
if (newValue !== oldValue) {
if (chatElementRef) {
chatElementRef.introMessage = { text: `你好!我是${newValue},有什么事情需要我帮忙吗?` }
}
}
}
)

/*********************************** deep-chat 对话配置 ***********************************/

function watchAndUpdateConfig() {
watch(
[selectedCapabilities, temperatureValue, agentForm],
([newCapabilities, newTemperature, newAgentForm]) => {
// 更新 debugConversationConfig
debugConversationConfig.agent_config = {
...debugConversationConfig.agent_config,
tool_config: newCapabilities,
temperature: newTemperature,
agent_name: newAgentForm.agent_name,
agent_abstract: newAgentForm.agent_abstract,
agent_info: newAgentForm.agent_info,
max_tokens: newAgentForm.max_tokens,
agent_enable: true
}
console.log('更新:', newCapabilities, newTemperature, newAgentForm)
},
{ deep: true } // 使用深度监视以捕获 agentForm 内部的变化
)
}

onMounted(() => {
chatElementRef = document.getElementById('chat-element') as DeepChat
chatElementRef.introMessage = {
text: `你好!我是${agentForm.agent_name},有什么事情需要我帮忙吗?`
}
chatElementRef.connect = {
handler: async (body, signals: Signals) => {
handleDebugConversation(body, signals, chatElementRef as DeepChat)
}
}
watchAndUpdateConfig()
})

/******************************************************************************/

import { useConversation } from './conversationApi'
import { Tool } from './toolConfig'
import IconifyIconOffline from '../components/ReIcon/src/iconifyIconOffline'

const { handleDebugConversation, debugConversationConfig } = useConversation()

const chatHistory = reactive([
{
content: `${agentForm.agent_abstract}`,
role: 'user'
},
{
content: `${agentForm.agent_info}`,
role: 'user'
}
]) // 初始化 chatHistory

watch(
() => [agentForm.agent_abstract, agentForm.agent_info],
([newAbstract, newInfo], [oldAbstract, oldInfo]) => {
if (newAbstract !== oldAbstract) {
chatHistory[0].content = newAbstract
}
if (newInfo !== oldInfo) {
chatHistory[1].content = newInfo
}
if (chatElementRef) {
chatElementRef.clearMessages()
}
}
)

/*********************************** 对话配置 ***********************************/

const {
configs,

fetchAllConfigs
} = useConfigManagement()

const debugConfigSettings = ref({
config_id: '',
config_name: ''
})

const selectDebugModel = (config: ModelConfig) => {
if (config) {
debugConfigSettings.value.config_id = config.config_id as string
debugConfigSettings.value.config_name = config.config_name
debugConversationConfig.config_id = Number.parseInt(config.config_id as string, 10)
console.log('debugConversationConfig.config_id:', debugConversationConfig.config_id)
}
}

onMounted(async () => {
await fetchAllConfigs()
})

/******************************************************************************/

/*********************************** 对话设置 ***********************************/

/******************************************************************************/

//从对话界面跳转到配置界面修改的处理逻辑
const route = useRoute()
const agentId = ref<string>('')

onMounted(async () => {
agentId.value = route.query.agentId as string
if (agentId.value) {
await fetchAgentInfo(agentId.value)
}
})

const fetchAgentInfo = async (id) => {
try {
const response = await axios.get(`http://127.0.0.1:7861/api/agent/get_agent?agent_id=${id}`)
console.log(response)
if (response.data.code === 200) {
const agentData = response.data.data
// 将获取的数据填充到表单中
agentForm.agent_name = agentData.agent_name
agentForm.agent_abstract = agentData.agent_abstract
agentForm.agent_info = agentData.agent_info
temperatureValue.value = agentData.temperature
agentForm.max_tokens = agentData.max_tokens
selectedCapabilities.value = agentData.tool_config
avatarIcon.value = agentData.avatar || uploadIcon
// 其他字段同理...
}
} catch (error) {
console.error('Error fetching agent info:', error)
ElMessage.error('获取Agent信息失败')
}
}

const saveConfig = async () => {
try {
const avatarToUpload = avatarIcon.value === uploadIcon ? '' : avatarIcon.value
const url = agentId.value
? 'http://127.0.0.1:7861/api/agent/update_agent'
: 'http://127.0.0.1:7861/api/agent/create_agent'
const method = agentId.value ? 'put' : 'post'

const data = {
agent_id: agentId.value,
agent_name: agentForm.agent_name,
agent_abstract: agentForm.agent_abstract,
agent_info: agentForm.agent_info,
temperature: temperatureValue.value,
max_tokens: agentForm.max_tokens,
tool_config: selectedCapabilities.value,
avatar: avatarToUpload
}

const response = await axios[method](url, data, {
headers: {
'Content-Type': 'application/json'
}
})

if (response.data.code === 200) {
ElMessage.success({
message: agentId.value ? '成功更新Agent配置' : '成功保存Agent配置',
type: 'success'
})
router.push('/') // 保存后返回主页面
} else {
ElMessage.error({
message: response.data.msg,
type: 'error'
})
}
} catch (error) {
console.error('Error saving agent config:', error)
ElMessage.error('保存配置失败')
}
}

/*********************************** 监视聊天框是否可用 ***********************************/

const showTooltip = ref(false)

const isPreviewDisabled = computed(() => {
return !agentForm.agent_name || !debugConfigSettings.value.config_name
})

const tooltipContent = computed(() => {
if (!agentForm.agent_name && !debugConfigSettings.value.config_name) {
return '请设置 Agent 名称并选择配置'
} else if (!agentForm.agent_name) {
return '请设置 Agent 名称'
} else if (!debugConfigSettings.value.config_name) {
return '请选择配置'
}
return ''
})

/******************************************************************************/
</script>

<style scoped>
.agent-avatar:hover {
cursor: pointer;
}

.custom-tooltip {
font-size: 16px !important; /* 增大字体大小 */
padding: 10px 12px !important; /* 增加内边距 */
}

:deep(.intro-panel) {
display: block;
}

.config-button {
font-size: 16px;
padding: 10px 20px;
}

.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
}

.left-icons {
display: flex;
align-items: center;
}

.back-button {
margin-right: 10px;
padding: 0;
}

.back-icon {
width: 25px;
height: 25px;
}

.content-container {
flex: 1;
overflow: hidden;
display: flex;
}

.preview-container {
position: relative;
width: 50%;
padding: 20px;
display: flex;
flex-direction: column;
border: 1px solid #ddd;
border-radius: 10px;
}

.preview-container.disabled .preview-area {
opacity: 0.5;
pointer-events: none;
}

.preview-area {
flex: 1;
background-color: #f9f9f9;
overflow: hidden;
display: flex;
position: relative;
}

.cursor-not-allowed {
cursor: not-allowed;
}

.overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.5);
z-index: 10;
cursor: not-allowed;
}

.preview-area {
flex: 1;
background-color: #f9f9f9;
overflow: hidden;
display: flex;
}

deep-chat {
flex: 1;
width: 100%;
height: 100%;
}

.config-title {
font-size: 1.5em;
font-weight: bold;
padding-bottom: 20px;
}

.config-menu {
padding: 20px;
overflow-y: auto; /* Enable vertical scrolling */
border: 1px solid #ddd; /* Add this line to add a border */
border-radius: 10px; /* Optional: to add rounded corners */
}

.content-container {
flex: 1;
overflow: hidden;
display: flex; /* Add this to enable flex layout */
}

.full-height {
height: 98vh;
display: flex;
flex-direction: column;
}

.config-tips {
font-size: 12px;
color: #909399;
line-height: 1.5;
margin-top: 5px;
margin-bottom: 5px;
display: block;
}

.config-form {
font-weight: bold;
font-size: 1.2em;
}

.horizontal-menu {
font-weight: bold;
}
</style>

+ 214
- 0
MindpilotUI-1/src/renderer/src/views/configManagement.ts View File

@@ -0,0 +1,214 @@
import axios from "axios";
import { ref, reactive, computed } from "vue";
import { ElMessage } from "element-plus";

const API_BASE_URL = "http://127.0.0.1:7861/api/model_configs";

export interface ModelConfig {
config_id?: string;
config_name: string;
platform: string;
base_url: string;
api_key: string;
llm_model: {
model: string;
callbacks: boolean;
max_tokens: number;
temperature: number;
};
}


export const useConfigManagement = () => {
const isEditMode = computed(() => !!activeConfigId.value);
const isDeleteButtonDisabled = computed(() => !activeConfigId.value);
const configs = ref<ModelConfig[]>([]);
const activeConfigId = ref("");
const isShowConfigManagementDialog = ref(false);
const configManagementForm = reactive<ModelConfig>({
config_name: "",
platform: "",
base_url: "",
api_key: "",
llm_model: {
model: "",
callbacks: true,
max_tokens: 4096,
temperature: 1
}
});


const isSaveButtonDisabled = computed(() => {
// 如果是编辑模式,按钮总是启用的
if (isEditMode.value) {
return false;
}
// 如果是新建模式,只有当配置名称为空时才禁用按钮
return !configManagementForm.config_name.trim();
});

const fetchAllConfigs = async () => {
try {
const response = await axios.get(API_BASE_URL);
if (response.data.code === 200) {
configs.value = response.data.data;
} else {
ElMessage.error(response.data.msg);
}
} catch (error) {
ElMessage.error("无法获取配置");
console.error("无法获取配置:", error);
}
};

const fetchSingleConfig = async (configId) => {
try {
const response = await axios.get(`${API_BASE_URL}/${configId}`);
if (response.data.code === 200) {
Object.assign(configManagementForm, response.data.data);
activeConfigId.value = configId;
} else {
ElMessage.error(response.data.msg);
}
} catch (error) {
ElMessage.error("无法获取配置");
console.error("无法获取配置:", error);
}
};

const addNewConfig = async () => {
try {
const response = await axios.post(API_BASE_URL + "/add", configManagementForm);
if (response.data.code === 200) {
ElMessage.success("配置新建成功");
await fetchAllConfigs();
// isShowConfigManagementDialog.value = false;
return response.data.data; // 返回新创建的配置
} else {
ElMessage.error(response.data.msg);
}
} catch (error) {
ElMessage.error("添加配置失败");
console.error("Failed to add configuration:", error);
}
return null;
};
const handleNewConfig = () => {
activeConfigId.value = "";
Object.assign(configManagementForm, {
config_name: "",
platform: "",
base_url: "",
api_key: "",
llm_model: {
model: "",
callbacks: true,
max_tokens: 4096,
temperature: 1
}
});
};
if (isShowConfigManagementDialog.value) {
// 如果对话框已经打开,更新标题
const dialogEl = document.querySelector(".el-dialog__title");
if (dialogEl) {
dialogEl.textContent = "新建配置";
}
}

const updateConfig = async () => {
try {
const response = await axios.put(`${API_BASE_URL}/${activeConfigId.value}`, configManagementForm);
if (response.data.code === 200) {
ElMessage.success("配置更新成功");
await fetchAllConfigs();
// isShowConfigManagementDialog.value = false; // 不自动关闭
} else {
ElMessage.error(response.data.msg);
}
} catch (error) {
ElMessage.error("更新配置失败");
console.error("Failed to update configuration:", error);
}
};

const deleteConfig = async () => {
try {
const response = await axios.delete(`${API_BASE_URL}/${activeConfigId.value}`);
if (response.data.code === 200) {
ElMessage.success("已成功删除配置");

Object.assign(configManagementForm, {
config_name: "",
platform: "",
base_url: "",
api_key: "",
llm_model: {
model: "",
callbacks: true,
max_tokens: 4096,
temperature: 1
}
});

activeConfigId.value = "";

await fetchAllConfigs();
// isShowConfigManagementDialog.value = false;
} else {
ElMessage.error(response.data.msg);
}
} catch (error) {
ElMessage.error("无法删除配置");
console.error("Failed to delete configuration:", error);
}
};

const handleSaveConfig = async () => {
if (activeConfigId.value) {
await updateConfig();
} else {
const newConfig = await addNewConfig();
if (newConfig) {
activeConfigId.value = newConfig.config_id.toString();
}
}
};

const handleDeleteConfig = async () => {
if (activeConfigId.value) {
await deleteConfig();
} else {
ElMessage.warning("请选择要删除的配置");
}
};

const handleConfigSelect = async (configId) => {
await fetchSingleConfig(configId);
// 可以考虑添加一个小延迟,确保 activeConfigId 已经更新
setTimeout(() => {
if (isShowConfigManagementDialog.value) {
// 如果对话框已经打开,更新标题
const dialogEl = document.querySelector(".el-dialog__title");
if (dialogEl) {
dialogEl.textContent = "编辑配置";
}
}
}, 0);
};

return {
isEditMode,
configs,
activeConfigId,
isShowConfigManagementDialog,
configManagementForm,
fetchAllConfigs,
isSaveButtonDisabled,
handleConfigSelect, isDeleteButtonDisabled,
handleSaveConfig,
handleDeleteConfig,
handleNewConfig // 添加这行
};
};

+ 343
- 0
MindpilotUI-1/src/renderer/src/views/conversationApi.ts View File

@@ -0,0 +1,343 @@
import { reactive, ref, watch } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { clearMessageElement, extractFirstJSON } from './utils'
import { Signals } from 'deep-chat/dist/types/handler'
import type { DeepChat } from 'deep-chat'
import { generateAssistantWithRandomID } from '../utils/tools'
import { Agent } from './type'
import { useConversationStore } from '../store/store'

const API_BASE_URL = 'http://127.0.0.1:7861/api'

export interface Conversation {
conversation_id: string
title: string
created_at: string
updated_at: string
is_summarized: boolean
agent_id: number
}

export interface Message {
message_id: number
agent_status: number
role: string
text: string
files?: Array<{ name: string; src: string; type: string }>
timestamp: string
}

export interface SendMessage {
role: string
agent_id: number
config_id: number
text: string
files?: string[]
tool_config: string[]
temperature: number
max_tokens: number
}

export interface ChatHistory {
content: string
role: string
}

export interface DebugConversationConfig {
config_id: number | null
agent_config: Agent // 假设 Agent 类型已在其他地方定义
history: ChatHistory[]
}

export function useConversation() {
const conversations = ref<Conversation[]>([])
const currentConversation = ref<Conversation | null>(null)
const messages = ref<Message[]>([])
const error = ref<string | null>(null)
const localConversationConfig = ref<SendMessage>({
role: 'user',
agent_id: NaN,
config_id: NaN,
text: '',
tool_config: [],
temperature: 1,
max_tokens: 4096
})

watch(
localConversationConfig,
async (newValue) => {
console.log('localConversationConfig:', newValue)
},
{ deep: true }
)

watch(currentConversation, async (newValue) => {
useConversationStore().setCurrentConversation(newValue)
})

const createConversation = async (agent_id: number): Promise<Conversation> => {
try {
const response = await axios.post(`${API_BASE_URL}/conversation`, agent_id)
const newConversation: Conversation = response.data.data
currentConversation.value = newConversation
conversations.value.push(newConversation)
error.value = null
return newConversation
} catch (err) {
console.error('Failed to create conversation:', err)
error.value = 'Failed to create conversation'
throw err // Re-throw the error so it can be caught by the caller
}
}

const getConversations = async (): Promise<Conversation[]> => {
try {
const response = await axios.get(`${API_BASE_URL}/conversations`)
const fetchedConversations = response.data.data
conversations.value = fetchedConversations
error.value = null
return fetchedConversations
} catch (err) {
console.error('Failed to get conversations:', err)
error.value = 'Failed to get conversations'
return []
}
}

const getConversationDetails = async (conversation_id: string): Promise<void> => {
try {
const response = await axios.get(`${API_BASE_URL}/conversation/${conversation_id}`)
currentConversation.value = response.data.data
messages.value = response.data.data.messages
error.value = null
} catch (err) {
console.error('Failed to get conversation details:', err)
error.value = 'Failed to get conversation details'
}
}
const switchConversation = async (
conversation: Conversation,
chatElementRef: DeepChat
): Promise<void> => {
try {
await getConversationDetails(conversation.conversation_id)
currentConversation.value = conversation
console.log('localConversationConfig.value.agent_id:', localConversationConfig.value.agent_id)
console.log('conversation.agent_id:', conversation.agent_id)
localConversationConfig.value.agent_id = conversation.agent_id
error.value = null

// Clear the chat interface
if (chatElementRef) {
chatElementRef.clearMessages()
clearMessageElement(chatElementRef)
}

console.log(messages.value)
// 加载消息
messages.value.forEach((message) => {
if (chatElementRef) {
if (message.agent_status === 3) {
const extractJson = extractFirstJSON(message.text)
if (extractJson) {
if (extractJson['action'] === 'Final Answer') {
chatElementRef.addMessage({
text: extractJson['action_input'],
role: generateAssistantWithRandomID()
})
} else {
const htmlContent = `<json-collapse label="执行 ${extractJson['action']}" data-json='${JSON.stringify(extractJson)}'></json-collapse>`
chatElementRef.addMessage({
html: htmlContent,
role: generateAssistantWithRandomID()
})
}
} else {
chatElementRef.addMessage({
text: message.text,
role: generateAssistantWithRandomID()
})
}
} else if (message.agent_status === 7) {
const htmlContent = `<message-collapse data-message="${message.text}"></message-collapse>`
chatElementRef.addMessage({ html: htmlContent, role: generateAssistantWithRandomID() })
} else {
chatElementRef.addMessage({ text: message.text, role: message.role })
}
}
})
} catch (err) {
console.error('Failed to switch conversation:', err)
error.value = 'Failed to switch conversation'
}
}

const handleMessage = async (body, signals: Signals, chatElementRef: DeepChat) => {
console.log('localConversationConfig.value:', localConversationConfig.value)
console.log('body:', body)
const url = `${API_BASE_URL}/conversation/${currentConversation.value!.conversation_id}/messages`
const requestBody: SendMessage = {
role: 'user',
agent_id: localConversationConfig.value.agent_id,
config_id: localConversationConfig.value.config_id,
text: body.messages[0].text,
tool_config: localConversationConfig.value.tool_config,
temperature: localConversationConfig.value.temperature,
max_tokens: localConversationConfig.value.max_tokens
}
console.log('requestBody: ', requestBody)
try {
const response = await axios.post(url, requestBody, {
headers: {
accept: 'application/json',
'Content-Type': 'application/json'
}
})
if (response.data.code === 200) {
console.log('Response:', response)
const responseMessages = response.data.data
console.log('responseMessages:', responseMessages)
for (let i = 0; i < responseMessages.length; i++) {
if (i !== responseMessages.length - 1 && responseMessages[i].agent_status === 3) {
const extractJson = extractFirstJSON(responseMessages[i].text)
if (extractJson) {
const htmlContent = `<json-collapse label="执行 ${extractJson['action']}" data-json='${JSON.stringify(extractJson)}'></json-collapse>`
chatElementRef.addMessage({
html: htmlContent,
role: generateAssistantWithRandomID()
})
} else {
chatElementRef.addMessage({
text: responseMessages[i].text,
role: generateAssistantWithRandomID()
})
}
} else if (responseMessages[i].agent_status === -1) {
signals.onResponse({
text: responseMessages[0].text,
role: generateAssistantWithRandomID()
})
} else if (responseMessages[i].agent_status === 7) {
const htmlContent = `<message-collapse data-message="${responseMessages[i].text}"></message-collapse>`
chatElementRef.addMessage({ html: htmlContent, role: generateAssistantWithRandomID() })
} else if (i === responseMessages.length - 1) {
const extractJson = extractFirstJSON(responseMessages[i].text)
if (extractJson) {
const finalAnswer = extractJson['action_input']
signals.onResponse({ text: finalAnswer, role: generateAssistantWithRandomID() })
}
}
}
await getConversationDetails(currentConversation.value!.conversation_id)
} else {
signals.onResponse({ error: '发送消息时发生错误,请重试' })
}
} catch (error) {
ElMessage.error('发送消息时发生错误,请重试')
signals.onResponse({ error: '发送消息时发生错误,请重试' })
}
}

const debugConversationConfig: DebugConversationConfig = reactive({
config_id: null,
agent_config: {} as Agent,
history: []
})

const handleDebugConversation = async (body, signals: Signals, chatElementRef: DeepChat) => {
const url = `${API_BASE_URL}/conversation/debug`
const requestBody = {
config_id: debugConversationConfig.config_id,
query: body.messages[0].text,
history: debugConversationConfig.history,
agent_config: debugConversationConfig.agent_config
}
console.log('requestBody: ', requestBody)
try {
const response = await axios.post(url, requestBody, {
headers: {
accept: 'application/json',
'Content-Type': 'application/json'
}
})
if (response.data.code === 200) {
console.log('Response:', response)
const responseMessages = response.data.data
console.log('responseMessages:', responseMessages)
for (let i = 0; i < responseMessages.length; i++) {
if (i !== responseMessages.length - 1 && responseMessages[i].agent_status === 3) {
const extractJson = extractFirstJSON(responseMessages[i].text)
if (extractJson) {
console.log('exractJson: ', extractJson)
const htmlContent = `<json-collapse label="执行 ${extractJson['action']}" data-json='${JSON.stringify(extractJson)}'></json-collapse>`
chatElementRef.addMessage({
html: htmlContent,
role: generateAssistantWithRandomID()
})
} else {
chatElementRef.addMessage({
text: responseMessages[i].text,
role: generateAssistantWithRandomID()
})
}
} else if (responseMessages[i].agent_status === -1) {
signals.onResponse({
text: responseMessages[0].text,
role: generateAssistantWithRandomID()
})
} else if (responseMessages[i].agent_status === 7) {
const htmlContent = `<message-collapse data-message="${responseMessages[i].text}"></message-collapse>`
chatElementRef.addMessage({ html: htmlContent, role: generateAssistantWithRandomID() })
} else if (i === responseMessages.length - 1) {
const extractJson = extractFirstJSON(responseMessages[i].text)
if (extractJson) {
const finalAnswer = extractJson['action_input']
signals.onResponse({ text: finalAnswer, role: generateAssistantWithRandomID() })
}
}
}
} else {
signals.onResponse({ error: '发送消息时发生错误,请重试' })
}
} catch (error) {
console.log(error)
ElMessage.error('发送消息时发生错误,请重试')
signals.onResponse({ error: '发送消息时发生错误,请重试' })
}
}

const deleteConversation = async (conversation_id: string): Promise<void> => {
try {
await axios.delete(`${API_BASE_URL}/conversation/${conversation_id}`)
conversations.value = conversations.value.filter(
(conv) => conv.conversation_id !== conversation_id
)
if (currentConversation.value?.conversation_id === conversation_id) {
currentConversation.value = null
messages.value = []
}
error.value = null
} catch (err) {
console.error('Failed to delete conversation:', err)
error.value = 'Failed to delete conversation'
}
}

return {
conversations,
currentConversation,
messages,
error,
createConversation,
getConversations,
debugConversationConfig,
getConversationDetails,
handleDebugConversation,
deleteConversation,
localConversationConfig,
switchConversation,
handleMessage
}
}

+ 67
- 0
MindpilotUI-1/src/renderer/src/views/conversationSummary.ts View File

@@ -0,0 +1,67 @@
const conversationSummaryQueryPrompt: string = `\n \n \n \n 请总结以上内容,总结的内容精炼且不超过5个字,并以以下JSON格式输出:\n \n
{
"summary": "对话的总结内容"
}`;

const axiosHanders = {
"accept": "application/json",
"Content-Type": "application/json"
};


export const conversationSummary = async (chatConversation: string) => {

console.log("Conversation summary: ", chatConversation);

const requestPrompt = chatConversation + conversationSummaryQueryPrompt;
const requestBody = {
query: requestPrompt,
history: [],
stream: false,
agent_enable: false,
tool_config: [],
chat_model_config: {
api_key: "sk-cERDW9Fr2ujq8D2qYck9cpc9MtPytN26466bunfYXZVZWV7Y",
base_url: "https://api.chatanywhere.tech/v1/",
is_openai: true,
llm_model: {
"gpt-4o": {
callbacks: true,
max_tokens: 8192,
temperature: 0.8
}
},
platform: "OpenAI",

agent_id: -1
}
};

try {
const response = await fetch("http://127.0.0.1:7861/chat/chat/online", {
method: "POST",
headers: {
...axiosHanders
},
body: JSON.stringify(requestBody)
});

if (!response.ok) {
throw new Error("Network response was not ok");
}

const responseData = await response.json();
const messageContent = responseData.choices[0].message.content;
//正则匹配messageContent中可能的json结构
const jsonMatch = messageContent.match(/\{.*\}/s);
console.log("jsonMatch", jsonMatch);
if (jsonMatch) {
const jsonObject = JSON.parse(jsonMatch[0]);
console.log(jsonObject);
return jsonObject;
}
} catch (error) {
console.error("Error summarizing conversation:", error);
return "总结失败";
}
};

+ 153
- 0
MindpilotUI-1/src/renderer/src/views/customComponents.ts View File

@@ -0,0 +1,153 @@
export class JsonCollapse extends HTMLElement {
private header: HTMLDivElement | null = null;
private content: HTMLDivElement | null = null;

constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot!.innerHTML = `
<style>
:host {
display: block;
border: 1px solid #ccc;
border-radius: 8px;
font-family: 'Courier New', Courier, monospace;
}
#header {
background-color: #f1f1f1;
padding: 2px;
cursor: pointer;
font-size: 14px;
}
#content {
padding: 10px;
display: none;
white-space: pre-wrap;
background-color: #f9f9f9;
}
</style>
<div id="header">${this.getAttribute("label") || "JSON"}</div>
<div id="content"></div>
`;

this.header = this.shadowRoot!.querySelector("#header");
this.content = this.shadowRoot!.querySelector("#content");

this.header?.addEventListener("click", this.toggleContent.bind(this));
}

connectedCallback() {
this.renderJson();
}

static get observedAttributes() {
return ["data-json"];
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
attributeChangedCallback(name: string, _oldValue: string, _newValue: string) {
if (name === "data-json") {
this.renderJson();
}
}

private toggleContent() {
if (this.content) {
this.content.style.display = this.content.style.display === "block" ? "none" : "block";
}
}

private renderJson() {
const json = this.getAttribute("data-json");
if (this.content) {
try {
const obj = JSON.parse(json || "{}");
const formattedJson = JSON.stringify(obj, null, 2);
this.content.textContent = formattedJson;
} catch (e) {
this.content.textContent = "Invalid JSON";
}
}
}
}


export class MessageCollapse extends HTMLElement {
private header: HTMLDivElement | null = null;
private content: HTMLDivElement | null = null;
private isCollapsed = true;

constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot!.innerHTML = `
<style>
:host {
display: block;
border: 1px solid #ccc;
border-radius: 8px;
font-family: 'Arial', sans-serif;
}
#header {
background-color: #f1f1f1;
padding: 10px;
cursor: pointer;
font-size: 14px;
}
#content {
padding: 10px;
display: none;
white-space: pre-wrap;
background-color: #f9f9f9;
}
</style>
<div id="header"></div>
<div id="content"></div>
`;

this.header = this.shadowRoot!.querySelector("#header");
this.content = this.shadowRoot!.querySelector("#content");

this.header?.addEventListener("click", this.toggleContent.bind(this));
}

connectedCallback() {
this.renderMessage();
}

static get observedAttributes() {
return ["data-message"];
}

attributeChangedCallback(name: string, _oldValue: string, _newValue: string) {
if (name === "data-message") {
this.renderMessage();
}
}

private toggleContent() {
if (this.content) {
this.isCollapsed = !this.isCollapsed;
this.content.style.display = this.isCollapsed ? "none" : "block";
this.header!.textContent = this.isCollapsed ? this.getCollapsedMessage() : this.getFullMessage();
}
}

private renderMessage() {
const message = this.getAttribute("data-message") || "";
if (this.header && this.content) {
this.header.textContent = this.getCollapsedMessage();
this.content.textContent = message;
}
}

private getCollapsedMessage() {
const message = this.getAttribute("data-message") || "";
return message.length > 50 ? message.substring(0, 50) + "..." : message;
}

private getFullMessage() {
return this.getAttribute("data-message") || "";
}
}


+ 1092
- 0
MindpilotUI-1/src/renderer/src/views/home.vue
File diff suppressed because it is too large
View File


+ 363
- 0
MindpilotUI-1/src/renderer/src/views/knowledgebase/example.ts View File

@@ -0,0 +1,363 @@
import { ref } from "vue";

export const products = ref([
{
type: 1,
isSetup: true,
name: "商店系统",
description: "管理您的在线商店和产品目录"
},
{
type: 2,
isSetup: false,
name: "预约系统",
description: "管理客户预约和日程安排"
},
{
type: 3,
isSetup: true,
name: "客户服务中心",
description: "处理客户查询和支持请求"
},
{
type: 4,
isSetup: true,
name: "用户管理系统",
description: "管理用户账户和权限设置"
},
{
type: 5,
isSetup: false,
name: "库存管理系统",
description: "追踪和管理产品库存"
},
{
type: 1,
isSetup: true,
name: "支付网关",
description: "处理在线支付交易"
},
{
type: 2,
isSetup: true,
name: "员工排班系统",
description: "管理员工工作时间和排班"
},
{
type: 3,
isSetup: false,
name: "知识库系统",
description: "集中存储和管理公司信息"
},
{
type: 4,
isSetup: true,
name: "会员管理系统",
description: "管理会员信息和忠诚度计划"
},
{
type: 5,
isSetup: true,
name: "数据分析平台",
description: "分析业务数据和生成报告"
},
{
type: 1,
isSetup: false,
name: "多渠道销售系统",
description: "整合多个销售渠道的订单管理"
},
{
type: 2,
isSetup: true,
name: "会议室预订系统",
description: "管理公司会议室的预订"
},
{
type: 3,
isSetup: true,
name: "工单管理系统",
description: "跟踪和管理客户服务工单"
},
{
type: 4,
isSetup: false,
name: "社交媒体管理",
description: "管理多个社交媒体账户和内容"
},
{
type: 5,
isSetup: true,
name: "项目管理工具",
description: "跟踪项目进度和资源分配"
},
{
type: 1,
isSetup: true,
name: "折扣管理系统",
description: "创建和管理促销活动和折扣"
},
{
type: 2,
isSetup: false,
name: "培训管理系统",
description: "组织和跟踪员工培训课程"
},
{
type: 3,
isSetup: true,
name: "聊天机器人平台",
description: "部署智能客服聊天机器人"
},
{
type: 4,
isSetup: true,
name: "反馈收集系统",
description: "收集和分析客户反馈"
},
{
type: 5,
isSetup: false,
name: "资产管理系统",
description: "跟踪公司资产和设备"
},
{
type: 1,
isSetup: true,
name: "物流跟踪系统",
description: "实时跟踪订单配送状态"
},
{
type: 2,
isSetup: true,
name: "绩效评估系统",
description: "管理员工绩效评估流程"
},
{
type: 3,
isSetup: false,
name: "文档管理系统",
description: "存储和组织公司文档"
},
{
type: 4,
isSetup: true,
name: "电子邮件营销平台",
description: "创建和管理电子邮件营销活动"
},
{
type: 5,
isSetup: true,
name: "业务流程自动化",
description: "自动化重复性业务流程"
},
{
type: 1,
isSetup: false,
name: "退货管理系统",
description: "处理产品退货和退款"
},
{
type: 2,
isSetup: true,
name: "远程办公管理",
description: "管理远程工作团队和任务"
},
{
type: 3,
isSetup: true,
name: "多语言支持系统",
description: "为客户提供多语言支持服务"
},
{
type: 4,
isSetup: false,
name: "内容管理系统",
description: "管理网站和营销内容"
},
{
type: 5,
isSetup: true,
name: "风险评估工具",
description: "评估和管理业务风险"
}, {
type: 1,
isSetup: true,
name: "商店系统",
description: "管理您的在线商店和产品目录"
},
{
type: 2,
isSetup: false,
name: "预约系统",
description: "管理客户预约和日程安排"
},
{
type: 3,
isSetup: true,
name: "客户服务中心",
description: "处理客户查询和支持请求"
},
{
type: 4,
isSetup: true,
name: "用户管理系统",
description: "管理用户账户和权限设置"
},
{
type: 5,
isSetup: false,
name: "库存管理系统",
description: "追踪和管理产品库存"
},
{
type: 1,
isSetup: true,
name: "支付网关",
description: "处理在线支付交易"
},
{
type: 2,
isSetup: true,
name: "员工排班系统",
description: "管理员工工作时间和排班"
},
{
type: 3,
isSetup: false,
name: "知识库系统",
description: "集中存储和管理公司信息"
},
{
type: 4,
isSetup: true,
name: "会员管理系统",
description: "管理会员信息和忠诚度计划"
},
{
type: 5,
isSetup: true,
name: "数据分析平台",
description: "分析业务数据和生成报告"
},
{
type: 1,
isSetup: false,
name: "多渠道销售系统",
description: "整合多个销售渠道的订单管理"
},
{
type: 2,
isSetup: true,
name: "会议室预订系统",
description: "管理公司会议室的预订"
},
{
type: 3,
isSetup: true,
name: "工单管理系统",
description: "跟踪和管理客户服务工单"
},
{
type: 4,
isSetup: false,
name: "社交媒体管理",
description: "管理多个社交媒体账户和内容"
},
{
type: 5,
isSetup: true,
name: "项目管理工具",
description: "跟踪项目进度和资源分配"
},
{
type: 1,
isSetup: true,
name: "折扣管理系统",
description: "创建和管理促销活动和折扣"
},
{
type: 2,
isSetup: false,
name: "培训管理系统",
description: "组织和跟踪员工培训课程"
},
{
type: 3,
isSetup: true,
name: "聊天机器人平台",
description: "部署智能客服聊天机器人"
},
{
type: 4,
isSetup: true,
name: "反馈收集系统",
description: "收集和分析客户反馈"
},
{
type: 5,
isSetup: false,
name: "资产管理系统",
description: "跟踪公司资产和设备"
},
{
type: 1,
isSetup: true,
name: "物流跟踪系统",
description: "实时跟踪订单配送状态"
},
{
type: 2,
isSetup: true,
name: "绩效评估系统",
description: "管理员工绩效评估流程"
},
{
type: 3,
isSetup: false,
name: "文档管理系统",
description: "存储和组织公司文档"
},
{
type: 4,
isSetup: true,
name: "电子邮件营销平台",
description: "创建和管理电子邮件营销活动"
},
{
type: 5,
isSetup: true,
name: "业务流程自动化",
description: "自动化重复性业务流程"
},
{
type: 1,
isSetup: false,
name: "退货管理系统",
description: "处理产品退货和退款"
},
{
type: 2,
isSetup: true,
name: "远程办公管理",
description: "管理远程工作团队和任务"
},
{
type: 3,
isSetup: true,
name: "多语言支持系统",
description: "为客户提供多语言支持服务"
},
{
type: 4,
isSetup: false,
name: "内容管理系统",
description: "管理网站和营销内容"
},
{
type: 5,
isSetup: true,
name: "风险评估工具",
description: "评估和管理业务风险"
}
]);

+ 302
- 0
MindpilotUI-1/src/renderer/src/views/knowledgebase/filelistdialog.vue View File

@@ -0,0 +1,302 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="`${kbName} 的文件列表`"
width="80%"
:fullscreen="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
custom-class="fixed-size-dialog"
>
<div class="dialog-header">
<div class="left-side">
<el-button @click="handleNewFile">
<Icon icon="material-symbols:add" />
&nbsp; 新建文件
</el-button>
</div>
<div class="right-side">
<el-input v-model="searchValue" style="width: 300px" placeholder="请输入文件名称" clearable>
<template #suffix>
<Icon v-show="searchValue.length === 0" icon="ri:search-line" />
</template>
</el-input>
</div>
</div>

<div class="file-list-container" @dragover.prevent @drop.prevent="handleDrop">
<el-scrollbar height="500px">
<el-row :gutter="16">
<el-col
v-for="doc in filteredDocuments"
:key="doc.file_name"
:xs="24"
:sm="12"
:md="8"
:lg="6"
:xl="4"
>
<ReFileCard
:file-name="doc.file_name"
:last-updated="doc.create_time"
:knowledge-base-name="kbName"
@delete="handleDeleteFile"
/>
</el-col>
</el-row>
</el-scrollbar>
</div>

<!-- 新建文件对话框 -->
<el-dialog
v-model="newFileDialogVisible"
title="新建文件"
width="500px"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<el-form :model="uploadOptions" label-width="150px">
<el-form-item label="覆盖已有文件">
<el-switch v-model="uploadOptions.override" />
</el-form-item>
<el-form-item label="上传后向量化">
<el-switch v-model="uploadOptions.to_vector_store" />
</el-form-item>
<el-form-item label="单段文本最大长度">
<el-input-number v-model="uploadOptions.chunk_size" :min="1" />
</el-form-item>
<el-form-item label="相邻文本重合长度">
<el-input-number v-model="uploadOptions.chunk_overlap" :min="0" />
</el-form-item>
<el-form-item label="中文标题加强">
<el-switch v-model="uploadOptions.zh_title_enhance" />
</el-form-item>
<el-form-item label="暂不保存向量库">
<el-switch v-model="uploadOptions.not_refresh_vs_cache" />
</el-form-item>
<el-form-item label="选择文件">
<el-upload
class="file-upload"
:auto-upload="false"
:on-change="handleNewFileChange"
:file-list="newFileUpload"
multiple
>
<el-button type="primary">选择文件</el-button>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="newFileDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleUploadNewFile">确定</el-button>
</span>
</template>
</el-dialog>

<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">关闭</el-button>
</span>
</template>
</el-dialog>
</template>

<script setup lang="ts">
import { ref, watch, computed, reactive } from 'vue'
import ReFileCard from '../../components/fileCard.vue'
import { useKnowledgeBase } from './kbapi'
import { Icon } from '@iconify/vue'
import { ElMessage } from 'element-plus'

defineOptions({
name: 'FileListDialog'
})

const searchValue = ref('')
const newFileUpload = ref<[]>([])
const newFileDialogVisible = ref(false)
const uploadOptions = reactive({
override: false,
to_vector_store: true,
chunk_size: 250,
chunk_overlap: 50,
zh_title_enhance: false,
not_refresh_vs_cache: false
})

const filteredDocuments = computed(() => {
return props.documents.filter((doc) =>
doc.file_name.toLowerCase().includes(searchValue.value.toLowerCase())
)
})

const handleNewFileChange = (file: File, fileList: File[]) => {
newFileUpload.value = fileList.map((file) => file.raw) as []
}

interface Props {
visible: boolean
kbName: string
documents: {
kb_name: string
file_name: string
file_ext: string
file_version: number
document_loader: string
docs_count: number
text_splitter: string
create_time: string
in_folder: boolean
in_db: boolean
file_mtime: number
file_size: number
custom_docs: boolean
No: number
}[]
}

const props = defineProps<Props>()
const emit = defineEmits(['update:visible', 'refresh-documents'])

const dialogVisible = ref(props.visible)
const { deleteDocument, fetchDocuments, uploadDocument } = useKnowledgeBase()

watch(
() => props.visible,
(newValue) => {
dialogVisible.value = newValue
}
)

watch(dialogVisible, (newValue) => {
emit('update:visible', newValue)
})

const handleNewFile = () => {
newFileDialogVisible.value = true
}
const handleUploadNewFile = async () => {
const files = newFileUpload.value
if (files.length === 0) {
ElMessage.warning('请选择要上传的文件')
return
}

const success = await uploadDocument(files, props.kbName, uploadOptions)

if (success) {
newFileDialogVisible.value = false
await refreshDocuments()
}
// Clear the file list after upload attempt
newFileUpload.value = []
}

const handleDrop = async (e: DragEvent) => {
e.preventDefault()
const files = e.dataTransfer?.files
if (files && files.length > 0) {
for (let i = 0; i < files.length; i++) {
try {
const success = await uploadDocument([files[i]], props.kbName)
if (success) {
ElMessage.success(`文件 ${files[i].name} 上传成功`)
}
} catch (error) {
if (error instanceof Error) {
ElMessage.error(`文件 ${files[i].name} 上传失败: ${error.message}`)
} else {
ElMessage.error(`文件 ${files[i].name} 上传失败: 未知错误`)
}
}
}
await refreshDocuments()
}
}

const handleDeleteFile = async (knowledgeBaseName: string, fileName: string) => {
try {
await deleteDocument(knowledgeBaseName, fileName)
await refreshDocuments()
} catch (error) {
console.error('删除文件失败:', error)
}
}

const refreshDocuments = async () => {
await fetchDocuments(props.kbName)
emit('refresh-documents')
}
</script>

<style scoped>
.fixed-size-dialog {
display: flex;
flex-direction: column;
height: 90vh;
max-height: 800px;
}

.fixed-size-dialog :deep(.el-dialog__body) {
flex: 1;
overflow: hidden;
padding: 10px;
}

.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}

.left-side {
display: flex;
align-items: center;
}

.right-side {
display: flex;
align-items: center;
}

.file-list-container {
height: 100%;
}

.el-row {
margin-bottom: -16px;
margin-right: -8px;
margin-left: -8px;
}

.el-col {
padding-bottom: 16px;
padding-right: 8px;
padding-left: 8px;
}

.file-upload {
display: inline-block;
}

.file-upload .el-upload {
width: auto;
}

.file-upload .el-upload-dragger {
width: auto;
height: auto;
border: none;
border-radius: 0;
}

.file-upload .el-upload-dragger:hover {
border: none;
}

.file-upload .el-button {
margin-right: 10px;
}
</style>

+ 280
- 0
MindpilotUI-1/src/renderer/src/views/knowledgebase/kbapi.ts View File

@@ -0,0 +1,280 @@
import axios from 'axios'
import { ref, reactive, computed } from 'vue'
import { ElLoading, ElMessage } from 'element-plus'

export const KB_API_BASE_URL = 'http://127.0.0.1:7861/knowledge_base'

export interface KnowledgeBase {
id: number
kb_name: string
kb_info: string
vs_type: string
embed_model: string
file_count: number
create_time: string
}

export interface Document {
kb_name: string
file_name: string
file_ext: string
file_version: number
document_loader: string
docs_count: number
text_splitter: string
create_time: string
in_folder: boolean
in_db: boolean
file_mtime: number
file_size: number
custom_docs: boolean
No: number
}

export interface UploadDocumentOptions {
override?: boolean
to_vector_store?: boolean
chunk_size?: number
chunk_overlap?: number
zh_title_enhance?: boolean
not_refresh_vs_cache?: boolean
}

export const useKnowledgeBase = () => {
const knowledgeBases = ref<KnowledgeBase[]>([])
const activeKnowledgeBase = ref('')
const isShowKnowledgeBaseDialog = ref(false)
const knowledgeBaseForm = reactive({
knowledge_base_name: '',
vector_store_type: 'faiss',
kb_info: ''
})
// 文档列表
const documents = ref<Document[]>([])

const isSaveButtonDisabled = computed(() => !knowledgeBaseForm.knowledge_base_name?.trim())

const fetchAllKnowledgeBases = async () => {
try {
const response = await axios.get(`${KB_API_BASE_URL}/list_knowledge_bases`)
if (response.data.code === 200) {
knowledgeBases.value = response.data.data
} else {
ElMessage.error(response.data.msg || '获取知识库列表失败')
}
} catch (error) {
ElMessage.error('无法获取知识库列表')
console.error('Failed to fetch knowledge bases:', error)
}
}

const createKnowledgeBase = async () => {
try {
const response = await axios.post(
`${KB_API_BASE_URL}/create_knowledge_base`,
knowledgeBaseForm
)
if (response.data.code === 200) {
ElMessage.success('知识库创建成功')
await fetchAllKnowledgeBases()
return response.data.data
} else {
ElMessage.error(response.data.msg)
}
} catch (error) {
ElMessage.error('创建知识库失败')
console.error('Failed to create knowledge base:', error)
}
return null
}
const deleteKnowledgeBase = async (kbName: string) => {
try {
const response = await axios.post(`${KB_API_BASE_URL}/delete_knowledge_base`, kbName)
if (response.data.code === 200) {
ElMessage.success('知识库删除成功')
await fetchAllKnowledgeBases()
activeKnowledgeBase.value = ''
} else {
ElMessage.error(response.data.msg || '删除知识库失败')
}
} catch (error) {
ElMessage.error('删除知识库失败')
console.error('Failed to delete knowledge base:', error)
}
}
const fetchDocuments = async (kbName: string) => {
try {
const response = await axios.get(`${KB_API_BASE_URL}/list_files`, {
params: { knowledge_base_name: kbName }
})
if (response.data.code === 200) {
documents.value = response.data.data
console.log('documents.value:', documents.value)
} else {
ElMessage.error(response.data.msg)
}
} catch (error) {
ElMessage.error('无法获取文档列表')
console.error('Failed to fetch documents:', error)
}
}

const uploadDocument = async (
file: File | File[],
kbName: string,
options: UploadDocumentOptions = {}
) => {
const formData = new FormData()

// 处理单个文件或多个文件
if (Array.isArray(file)) {
file.forEach((f) => formData.append('files', f))
} else {
formData.append('files', file)
}

formData.append('knowledge_base_name', kbName)

// 添加可选参数
if (options.override !== undefined) formData.append('override', options.override.toString())
if (options.to_vector_store !== undefined)
formData.append('to_vector_store', options.to_vector_store.toString())
if (options.chunk_size !== undefined)
formData.append('chunk_size', Math.floor(options.chunk_size).toString())
if (options.chunk_overlap !== undefined)
formData.append('chunk_overlap', Math.floor(options.chunk_overlap).toString())
if (options.zh_title_enhance !== undefined)
formData.append('zh_title_enhance', options.zh_title_enhance.toString())
if (options.not_refresh_vs_cache !== undefined)
formData.append('not_refresh_vs_cache', options.not_refresh_vs_cache.toString())

// 显示加载动画
const loadingInstance = ElLoading.service({
lock: true,
text: '文件上传中...',
background: 'rgba(0, 0, 0, 0.7)'
})

try {
const response = await axios.post(`${KB_API_BASE_URL}/upload_docs`, formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})

if (response.data.code === 200) {
ElMessage.success('文档上传成功')
// 假设 fetchDocuments 是一个更新文档列表的函数
await fetchDocuments(kbName)
return true
} else {
ElMessage.error(response.data.msg || '上传文档失败')
return false
}
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
// 处理 Axios 错误
const errorMessage =
error.response.data?.detail || error.response.data?.msg || '上传文档失败'
ElMessage.error(errorMessage)
console.error('Failed to upload document:', error.response.data)
} else {
// 处理非 Axios 错误
ElMessage.error('上传文档时发生未知错误')
console.error('Failed to upload document:', error)
}
return false
} finally {
// 关闭加载动画
loadingInstance.close()
}
}

const deleteDocument = async (kbName: string, fileName: string) => {
try {
const response = await axios.post(`${KB_API_BASE_URL}/delete_docs`, {
knowledge_base_name: kbName,
file_names: [fileName],
delete_content: true,
not_refresh_vs_cache: false
})
if (response.data.code === 200) {
ElMessage.success('文档删除成功')
await fetchDocuments(kbName)
} else {
ElMessage.error(response.data.msg)
}
} catch (error) {
ElMessage.error('删除文档失败')
console.error('Failed to delete document:', error)
}
}

const updateKnowledgeBaseInfo = async (kbName: string, kbInfo: string) => {
try {
const response = await axios.post(`${KB_API_BASE_URL}/update_info`, {
kb_name: kbName,
kb_info: kbInfo
})
if (response.data.code === 200) {
ElMessage.success('知识库信息更新成功')
await fetchAllKnowledgeBases()
} else {
ElMessage.error(response.data.msg)
}
} catch (error) {
ElMessage.error('更新知识库信息失败')
console.error('Failed to update knowledge base info:', error)
}
}

const handleNewKnowledgeBase = () => {
activeKnowledgeBase.value = ''
Object.assign(knowledgeBaseForm, {
kb_name: '',
vs_type: 'faiss',
kb_info: ''
})
}

const handleSaveKnowledgeBase = async () => {
if (activeKnowledgeBase.value) {
await updateKnowledgeBaseInfo(activeKnowledgeBase.value, knowledgeBaseForm.kb_info || '')
} else {
const newKnowledgeBase = await createKnowledgeBase()
if (newKnowledgeBase) {
activeKnowledgeBase.value = newKnowledgeBase.kb_name
}
}
}

const handleDeleteKnowledgeBase = async (kbName: string) => {
if (kbName) {
await deleteKnowledgeBase(kbName)
} else {
ElMessage.warning('请选择要删除的知识库')
}
}

const handleKnowledgeBaseSelect = async (kbName: string) => {
activeKnowledgeBase.value = kbName
await fetchDocuments(kbName)
}

return {
knowledgeBases,
activeKnowledgeBase,
isShowKnowledgeBaseDialog,
knowledgeBaseForm,
documents,
isSaveButtonDisabled,
fetchAllKnowledgeBases,
handleNewKnowledgeBase,
handleSaveKnowledgeBase,
handleDeleteKnowledgeBase,
handleKnowledgeBaseSelect,
uploadDocument,
deleteDocument,
deleteKnowledgeBase,
updateKnowledgeBaseInfo,
fetchDocuments
}
}

+ 261
- 0
MindpilotUI-1/src/renderer/src/views/knowledgebase/kbconfig.vue View File

@@ -0,0 +1,261 @@
<template>
<div class="kb-layout">
<el-container>
<el-header class="flex items-center">
<Icon icon="weui:back-filled" @click="goBack" class="cursor-pointer mr-2" />
<h1>知识中心</h1>
</el-header>
<el-main>
<div class="w-full flex justify-between mb-4">
<el-button @click="handleNewKnowledgeBaseClick">
<Icon icon="material-symbols:add" />
&nbsp; 新建知识库
</el-button>

<el-input
v-model="searchValue"
style="width: 300px"
placeholder="请输入知识库名称"
clearable
>
<template #suffix>
<Icon v-show="searchValue.length === 0" icon="ri:search-line" />
</template>
</el-input>
</div>
<div v-loading="dataLoading">
<el-empty
v-show="displayedKnowledgeBases.length === 0"
:description="`${searchValue} 知识库不存在`"
/>
<template v-if="pagination.total > 0">
<el-row :gutter="16">
<el-col
v-for="kb in displayedKnowledgeBases"
:key="kb.kb_name"
:xs="24"
:sm="12"
:md="8"
:lg="6"
:xl="4"
>
<ReCard
:product="mapKnowledgeBaseToProduct(kb)"
@delete-item="handleDeleteItem"
@manage-product="handleManageProduct"
/>
</el-col>
</el-row>
</template>
</div>
</el-main>
<el-footer>
<el-pagination
v-model:currentPage="pagination.current"
class="float-right mt-4"
:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[12, 24, 36]"
:background="true"
layout="total, sizes, prev, pager, next, jumper"
@size-change="onPageSizeChange"
@current-change="onCurrentChange"
/>
</el-footer>
</el-container>
<ListDialogForm v-model:visible="isShowKnowledgeBaseDialog" :data="knowledgeBaseForm" />
<FileListDialog
v-model:visible="isShowFileListDialog"
:kb-name="activeKnowledgeBase"
:documents="documents"
@refresh-documents="handleRefreshDocuments"
/>
<NewKBDialog
v-model:visible="isShowKnowledgeBaseDialog"
:loading="loading"
:form="knowledgeBaseForm"
@save="handleSaveKnowledgeBaseClick"
/>
</div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import ReCard from '../../components/ListCard.vue'
import { Icon } from '@iconify/vue'
import { useKnowledgeBase } from './kbapi'
import FileListDialog from './filelistdialog.vue'
import NewKBDialog from './newkbdialog.vue'
import { router } from '../../router'

const {
knowledgeBases,
activeKnowledgeBase,
isShowKnowledgeBaseDialog,
knowledgeBaseForm,
documents,
fetchAllKnowledgeBases,
handleNewKnowledgeBase,
handleSaveKnowledgeBase,
handleDeleteKnowledgeBase,
fetchDocuments
} = useKnowledgeBase()

const pagination = ref({ current: 1, pageSize: 12, total: 0 })
const dataLoading = ref(true)
const loading = ref(false)
const searchValue = ref('')
const isShowFileListDialog = ref(false)

const displayedKnowledgeBases = computed(() => {
const start = (pagination.value.current - 1) * pagination.value.pageSize
const end = start + pagination.value.pageSize
return knowledgeBases.value
.slice(start, end)
.filter((kb) => kb.kb_name.toLowerCase().includes(searchValue.value.toLowerCase()))
})

const mapKnowledgeBaseToProduct = (kb) => ({
name: kb.kb_name,
isSetup: true,
description: kb.kb_info,
type: 1,
fileCount: kb.file_count,
lastUpdatedAt: kb.create_time
})

onMounted(async () => {
dataLoading.value = true
await fetchAllKnowledgeBases()
pagination.value.total = knowledgeBases.value.length
dataLoading.value = false
})

const onPageSizeChange = (size: number) => {
pagination.value.pageSize = size
pagination.value.current = 1
}

const onCurrentChange = (current: number) => {
pagination.value.current = current
}

const handleDeleteItem = (product) => {
ElMessageBox.confirm(
product ? `确认删除后${product.name}的所有知识库信息将被清空, 且无法恢复` : '',
'提示',
{
type: 'warning'
}
)
.then(() => {
handleDeleteKnowledgeBase(product.name)
fetchAllKnowledgeBases()
})
.catch(() => {})
}

const handleManageProduct = (product) => {
handleKnowledgeBaseClick(product.name)
}

const handleKnowledgeBaseClick = async (kbName: string) => {
activeKnowledgeBase.value = kbName
await fetchDocuments(kbName)
isShowFileListDialog.value = true
}

const handleNewKnowledgeBaseClick = () => {
handleNewKnowledgeBase() // Reset the form
isShowKnowledgeBaseDialog.value = true // Show the dialog
}

const handleRefreshDocuments = async () => {
await fetchDocuments(activeKnowledgeBase.value)
await fetchAllKnowledgeBases()
}

const handleSaveKnowledgeBaseClick = async () => {
loading.value = true // 启动 Loading
try {
await handleSaveKnowledgeBase()
isShowKnowledgeBaseDialog.value = false
await fetchAllKnowledgeBases()
await nextTick()
} catch (error) {
ElMessage.error('创建知识库失败')
} finally {
loading.value = false // 关闭 Loading
}
}

const goBack = () => {
router.back()
}
</script>

<style lang="scss" scoped>
.icon-back {
margin-right: 8px;
}

.flex {
display: flex;
}

.items-center {
align-items: center;
}

.cursor-pointer {
cursor: pointer;
}

.mr-2 {
margin-right: 0.5rem;
}

.kb-layout {
//height: 100svh;
display: flex;
flex-direction: column;

.el-container {
height: 100%;
}

.el-header {
background-color: #f5f7fa;
color: #333;
}

.el-main {
overflow-y: auto;
}
}

.w-full {
width: 100%;
}

.flex {
display: flex;
}

.justify-between {
justify-content: space-between;
}

.mb-4 {
margin-bottom: 1rem;
}

.float-right {
float: right;
}

.mt-4 {
margin-top: 1rem;
}
</style>

+ 74
- 0
MindpilotUI-1/src/renderer/src/views/knowledgebase/newkbdialog.vue View File

@@ -0,0 +1,74 @@
<template>
<el-dialog v-model="dialogVisible" title="新建知识库" width="30%" @close="handleClose">
<div
v-loading="loading"
:element-loading-text="loadingText"
element-loading-spinner="el-icon-loading"
>
<el-form :model="form" label-width="120px">
<el-form-item label="知识库名称">
<el-input v-model="form.knowledge_base_name" />
</el-form-item>
<el-form-item label="向量库类型">
<el-select v-model="form.vector_store_type">
<el-option label="Faiss" value="faiss" />
<!-- Add other options if needed -->
</el-select>
</el-form-item>
<el-form-item label="知识库描述">
<el-input v-model="form.kb_info" type="textarea" />
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose" :disabled="loading">取消</el-button>
<el-button type="primary" @click="handleSave" :disabled="isSaveDisabled || loading">
确定
</el-button>
</span>
</template>
</el-dialog>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'

defineOptions({
name: 'NewKBDialog'
})

const props = defineProps<{
visible: boolean
loading: boolean
form: {
knowledge_base_name: string
vector_store_type: string
kb_info: string
}
}>()

const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'save'): void
}>()

const dialogVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
})

const isSaveDisabled = computed(() => !props.form.knowledge_base_name.trim())

const loadingText = ref('创建知识库中...')

const handleClose = () => {
if (!props.loading) {
emit('update:visible', false)
}
}

const handleSave = () => {
emit('save')
}
</script>

+ 34
- 0
MindpilotUI-1/src/renderer/src/views/toolConfig.ts View File

@@ -0,0 +1,34 @@
import { ref } from 'vue'
import axios from 'axios'

export interface Tool {
value: string
label: string
}

export const useToolConfig = () => {
const availableTools = ref<Tool[]>([])
const selectedTools = ref<string[]>([])



const fetchAvailableTools = async () => {
try {
const response = await axios.get('http://127.0.0.1:7861/api/tools/available_tools')
if (response.data && response.data.tools) {
availableTools.value = response.data.tools.map((tool: string) => ({
value: tool,
label: tool
}))
}
} catch (error) {
console.error('Failed to fetch available tools:', error)
}
}

return {
availableTools,
selectedTools,
fetchAvailableTools
}
}

+ 37
- 0
MindpilotUI-1/src/renderer/src/views/type.ts View File

@@ -0,0 +1,37 @@
export interface Agent {
agent_id?: number;
agent_name: string;
agent_abstract: string;
agent_info: string;
temperature: number;
max_tokens: number;
tool_config: string[];
kb_name?: string[];
avatar?: string;
agent_enable?: boolean;
}

export interface backendChat {
content: string;
role: string;
}

export interface HistoryItems {
id: string;
text: string;
timestamp: Date;
summarized: boolean;
}

export interface configManagementFormInterface {
config_name: string;
platform: string;
base_url: string;
api_key: string;
llm_model: {
model: string
callbacks: boolean
max_tokens: number
temperature: number
};
}

+ 32
- 0
MindpilotUI-1/src/renderer/src/views/utils.ts View File

@@ -0,0 +1,32 @@
import type { DeepChat } from 'deep-chat'

export function extractFirstJSON(text: string): any | null {
const jsonRegex = /\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}/
const match = text.match(jsonRegex)

if (match) {
try {
return JSON.parse(match[0])
} catch (error) {
console.warn('Failed to parse JSON:', error)
}
}

return null
}

export const clearMessageElement = (deepChatRef: DeepChat | null) => {
if (!deepChatRef) {
console.log('deepChatRef is not valid')
return
}
// 获取所有 class="outer-message-container" 的元素
const messagesElements = deepChatRef.shadowRoot!.querySelectorAll('.outer-message-container')
console.log('messagesElements:', messagesElements)

// 遍历并删除每个元素
messagesElements.forEach((element) => {
console.log('Removing element:', element)
element.remove()
})
}

+ 4
- 0
MindpilotUI-1/tsconfig.json View File

@@ -0,0 +1,4 @@
{
"files": [],
"references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }]
}

+ 8
- 0
MindpilotUI-1/tsconfig.node.json View File

@@ -0,0 +1,8 @@
{
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
"include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*"],
"compilerOptions": {
"composite": true,
"types": ["electron-vite/node"]
}
}

+ 18
- 0
MindpilotUI-1/tsconfig.web.json View File

@@ -0,0 +1,18 @@
{
"extends": "@electron-toolkit/tsconfig/tsconfig.web.json",
"include": [
"src/renderer/src/env.d.ts",
"src/renderer/src/**/*",
"src/renderer/src/**/*.vue",
"src/preload/*.d.ts"
],
"compilerOptions": {
"composite": true,
"baseUrl": ".",
"paths": {
"@renderer/*": [
"src/renderer/src/*"
]
}
}
}

+ 5594
- 0
MindpilotUI-1/yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save