Taohongrun 5 сар өмнө
commit
f17d45dcbb
100 өөрчлөгдсөн 10398 нэмэгдсэн , 0 устгасан
  1. 41 0
      .gitignore
  2. 6 0
      Dockerfile
  3. 86 0
      README.md
  4. 48 0
      database.sql
  5. 2 0
      front-end/.env.development
  6. 1 0
      front-end/.env.production
  7. 23 0
      front-end/.eslintrc.cjs
  8. 36 0
      front-end/.gitignore
  9. 8 0
      front-end/.prettierrc.json
  10. 46 0
      front-end/README.md
  11. 2 0
      front-end/env.d.ts
  12. 13 0
      front-end/index.html
  13. 5266 0
      front-end/package-lock.json
  14. 49 0
      front-end/package.json
  15. BIN
      front-end/public/favicon.ico
  16. 79 0
      front-end/scripts/generate-api.js
  17. 78 0
      front-end/src/App.vue
  18. BIN
      front-end/src/assets/SrpingAI知识点.png
  19. BIN
      front-end/src/assets/background.jpg
  20. BIN
      front-end/src/assets/cover.png
  21. BIN
      front-end/src/assets/logo.jpg
  22. 83 0
      front-end/src/components/image/image-upload.vue
  23. 92 0
      front-end/src/components/key-value/key-value-input.vue
  24. 135 0
      front-end/src/components/key-value/value-input.vue
  25. 19 0
      front-end/src/main.ts
  26. 34 0
      front-end/src/router/index.ts
  27. 19 0
      front-end/src/stores/home-store.ts
  28. 17 0
      front-end/src/typings/index.d.ts
  29. 6 0
      front-end/src/utils/api-instance.ts
  30. 22 0
      front-end/src/utils/common.ts
  31. 23 0
      front-end/src/utils/request.ts
  32. 383 0
      front-end/src/views/chat/chat-view.vue
  33. 21 0
      front-end/src/views/chat/components/markdown-message.vue
  34. 76 0
      front-end/src/views/chat/components/message-input.vue
  35. 112 0
      front-end/src/views/chat/components/message-row.vue
  36. 140 0
      front-end/src/views/chat/components/session-item.vue
  37. 50 0
      front-end/src/views/chat/components/text-loading.vue
  38. 69 0
      front-end/src/views/chat/store/chat-store.ts
  39. 57 0
      front-end/src/views/code/analyze/analyze-result-view.vue
  40. 39 0
      front-end/src/views/code/analyze/collapse-title.vue
  41. 159 0
      front-end/src/views/login/login-view.vue
  42. 168 0
      front-end/src/views/login/register-view.vue
  43. 14 0
      front-end/tsconfig.app.json
  44. 11 0
      front-end/tsconfig.json
  45. 17 0
      front-end/tsconfig.node.json
  46. 27 0
      front-end/vite.config.ts
  47. 210 0
      pom.xml
  48. 6 0
      src/main/dto/AiMessage.dto
  49. 6 0
      src/main/dto/AiSession.dto
  50. 10 0
      src/main/dto/User.dto
  51. 17 0
      src/main/java/io/github/qifan777/knowledge/ServerApplication.java
  52. 36 0
      src/main/java/io/github/qifan777/knowledge/ai/agent/AbstractAgent.java
  53. 14 0
      src/main/java/io/github/qifan777/knowledge/ai/agent/Agent.java
  54. 70 0
      src/main/java/io/github/qifan777/knowledge/ai/agent/chronologist/Chronologist.java
  55. 35 0
      src/main/java/io/github/qifan777/knowledge/ai/agent/computer/ComputerAssistant.java
  56. 18 0
      src/main/java/io/github/qifan777/knowledge/ai/agent/computer/CpuAnalyzer.java
  57. 34 0
      src/main/java/io/github/qifan777/knowledge/ai/agent/computer/DirectoryReader.java
  58. 43 0
      src/main/java/io/github/qifan777/knowledge/ai/document/DocumentController.java
  59. 53 0
      src/main/java/io/github/qifan777/knowledge/ai/message/AiMessage.java
  60. 98 0
      src/main/java/io/github/qifan777/knowledge/ai/message/AiMessageChatMemory.java
  61. 163 0
      src/main/java/io/github/qifan777/knowledge/ai/message/AiMessageController.java
  62. 25 0
      src/main/java/io/github/qifan777/knowledge/ai/message/AiMessageRepository.java
  63. 10 0
      src/main/java/io/github/qifan777/knowledge/ai/message/MessageInputWrapper.java
  64. 9 0
      src/main/java/io/github/qifan777/knowledge/ai/message/dto/AiMessageParams.java
  65. 9 0
      src/main/java/io/github/qifan777/knowledge/ai/message/dto/AiMessageWrapper.java
  66. 29 0
      src/main/java/io/github/qifan777/knowledge/ai/session/AiSession.java
  67. 55 0
      src/main/java/io/github/qifan777/knowledge/ai/session/AiSessionController.java
  68. 20 0
      src/main/java/io/github/qifan777/knowledge/ai/session/AiSessionRepository.java
  69. 35 0
      src/main/java/io/github/qifan777/knowledge/code/CodeAssistantAgent.java
  70. 33 0
      src/main/java/io/github/qifan777/knowledge/code/analyze/AnalyzeController.java
  71. 144 0
      src/main/java/io/github/qifan777/knowledge/code/analyze/AnalyzeFunction.java
  72. 170 0
      src/main/java/io/github/qifan777/knowledge/code/arthas/ArthasFunction.java
  73. 22 0
      src/main/java/io/github/qifan777/knowledge/code/graph/controller/CodeGraphController.java
  74. 23 0
      src/main/java/io/github/qifan777/knowledge/code/graph/entity/ClassNode.java
  75. 22 0
      src/main/java/io/github/qifan777/knowledge/code/graph/entity/MethodNode.java
  76. 7 0
      src/main/java/io/github/qifan777/knowledge/code/graph/repository/ClassNodeRepository.java
  77. 8 0
      src/main/java/io/github/qifan777/knowledge/code/graph/repository/MethodNodeRepository.java
  78. 276 0
      src/main/java/io/github/qifan777/knowledge/code/graph/service/CodeGraphBuilder.java
  79. 60 0
      src/main/java/io/github/qifan777/knowledge/code/graph/service/CodeGraphService.java
  80. 45 0
      src/main/java/io/github/qifan777/knowledge/demo/DocumentAnalyzerFunction.java
  81. 65 0
      src/main/java/io/github/qifan777/knowledge/demo/DocumentDemoController.java
  82. 160 0
      src/main/java/io/github/qifan777/knowledge/demo/MessageDemoController.java
  83. 82 0
      src/main/java/io/github/qifan777/knowledge/graph/GraphController.java
  84. 26 0
      src/main/java/io/github/qifan777/knowledge/graph/chunk/Chunk.java
  85. 133 0
      src/main/java/io/github/qifan777/knowledge/graph/chunk/ChunkController.java
  86. 9 0
      src/main/java/io/github/qifan777/knowledge/graph/chunk/ChunkRepository.java
  87. 19 0
      src/main/java/io/github/qifan777/knowledge/graph/company/Company.java
  88. 51 0
      src/main/java/io/github/qifan777/knowledge/graph/company/CompanyController.java
  89. 6 0
      src/main/java/io/github/qifan777/knowledge/graph/company/CompanyRepository.java
  90. 17 0
      src/main/java/io/github/qifan777/knowledge/graph/form/Form.java
  91. 70 0
      src/main/java/io/github/qifan777/knowledge/graph/form/FormController.java
  92. 6 0
      src/main/java/io/github/qifan777/knowledge/graph/form/FormRepository.java
  93. 16 0
      src/main/java/io/github/qifan777/knowledge/graph/manager/Manager.java
  94. 84 0
      src/main/java/io/github/qifan777/knowledge/graph/manager/ManagerController.java
  95. 6 0
      src/main/java/io/github/qifan777/knowledge/graph/manager/ManagerRepository.java
  96. 51 0
      src/main/java/io/github/qifan777/knowledge/graph/model/Form10K.java
  97. 50 0
      src/main/java/io/github/qifan777/knowledge/graph/model/Form13.java
  98. 26 0
      src/main/java/io/github/qifan777/knowledge/infrastructure/code/CodeAssistantProperties.java
  99. 40 0
      src/main/java/io/github/qifan777/knowledge/infrastructure/code/JavaParserUtils.java
  100. 89 0
      src/main/java/io/github/qifan777/knowledge/infrastructure/config/GlobalExceptionAdvice.java

+ 41 - 0
.gitignore

@@ -0,0 +1,41 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
+/src/main/resources/application-private.yml
+/.idea/
+/template/

+ 6 - 0
Dockerfile

@@ -0,0 +1,6 @@
+FROM openjdk:17
+ENV TZ=Asia/Shanghai
+RUN ln  -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+LABEL authors="TaoHongRun"
+COPY dive-into-spring-ai-1.0-SNAPSHOT.jar /app.jar
+ENTRYPOINT ["java", "-jar" ]

+ 86 - 0
README.md

@@ -0,0 +1,86 @@
+# 项目介绍
+
+本项目使用SpringAI教学,包含了SSE流/Agent智能体/FunctionCall/Embedding/VectorDatabase/RAG/Graph RAG/历史消息/图片生成/图片理解
+
+![项目内容](./front-end/src/assets/cover.png)
+
+![知识点](./front-end/src/assets/SrpingAI知识点.png)
+
+
+
+[文档地址](https://www.jarcheng.top/blog/project/spring-ai/intro.html)
+[视频地址](https://www.bilibili.com/video/BV14y411q7RN/)
+
+## 运行环境
+
+- Java 17
+- Node.js 18+
+- MySQL 8
+- DashScope API KEY(或者其他)
+- Redis-Stack
+
+  redis基础上拓展向量查询功能
+
+    ```shell
+    docker run -d \
+    --name redis-stack \
+    --restart=always \
+    -v redis-data:/data \
+    -p 6379:6379 \
+    -p 8001:8001 \
+    -e REDIS_ARGS="--requirepass 123456" redis/redis-stack:latest
+    ```
+
+- neo4j 5+
+
+  安装完neo4j访问`localhost:7474`, 默认的账号密码都是`neo4j`和`neo4j`。
+
+    ```shell
+    docker run \
+    -d \
+    -p 7474:7474 -p 7687:7687 \
+    -v neo4j-data:/data -v neo4j-data:/plugins \
+    --name neo4j \
+    -e NEO4J_apoc_export_file_enabled=true \
+    -e NEO4J_apoc_import_file_enabled=true \
+    -e NEO4J_apoc_import_file_use__neo4j__config=true \
+    -e NEO4JLABS_PLUGINS=\[\"apoc\"\] \
+    -e NEO4J_dbms_security_procedures_unrestricted=apoc.\\\* \
+    neo4j
+    ```
+
+## 运行步骤
+
+### 1.clone代码
+
+```shell
+git clone https://github.com/qifan777/KnowledgeBaseChatSpringAI
+```
+
+### 2. idea打开项目
+
+### 3. 修改配置文件
+
+修改application.yml中的API-KEY, MySQL, Redis-Stack, Neo4j配置
+### 4. 运行项目
+
+后端运行
+
+1. 运行ServerApplication.java
+2. target/generated-sources/annotations右键mark directory as/generated source root
+
+前端运行,在front-end目录下
+
+- npm run install
+- npm run api (先运行后端)
+- npm run dev
+
+
+## 联系方式
+
+付费远程运行/安装/定制开发联系微信:ljc666max
+
+其他关于程序运行安装报错请加QQ群:
+
+- 416765656(满)
+- 632067985

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 48 - 0
database.sql


+ 2 - 0
front-end/.env.development

@@ -0,0 +1,2 @@
+VITE_WEBSOCKET_URL=ws://localhost:8877/handshake
+VITE_API_PREFIX=/api

+ 1 - 0
front-end/.env.production

@@ -0,0 +1 @@
+VITE_API_PREFIX=

+ 23 - 0
front-end/.eslintrc.cjs

@@ -0,0 +1,23 @@
+/* eslint-env node */
+require('@rushstack/eslint-patch/modern-module-resolution')
+
+module.exports = {
+  root: true,
+  extends: [
+    'plugin:vue/vue3-essential',
+    'eslint:recommended',
+    '@vue/eslint-config-typescript',
+    '@vue/eslint-config-prettier/skip-formatting'
+  ],
+  parserOptions: {
+    ecmaVersion: 'latest'
+  },
+  rules: {
+    'prettier/prettier': [
+      'warn',
+      {
+        endOfLine: 'auto'
+      }
+    ]
+  }
+}

+ 36 - 0
front-end/.gitignore

@@ -0,0 +1,36 @@
+### IntelliJ IDEA ###
+/.idea/
+*.iws
+*.iml
+*.ipr
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+/src/apis/__generated/
+/scripts/ingredient-admin.tar

+ 8 - 0
front-end/.prettierrc.json

@@ -0,0 +1,8 @@
+{
+  "$schema": "https://json.schemastore.org/prettierrc",
+  "semi": false,
+  "tabWidth": 2,
+  "singleQuote": true,
+  "printWidth": 100,
+  "trailingComma": "none"
+}

+ 46 - 0
front-end/README.md

@@ -0,0 +1,46 @@
+# mall-admin
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
+
+## Type Support for `.vue` Imports in TS
+
+TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
+
+If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
+
+1. Disable the built-in TypeScript Extension
+    1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
+    2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
+2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vitejs.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Type-Check, Compile and Minify for Production
+
+```sh
+npm run build
+```
+
+### Lint with [ESLint](https://eslint.org/)
+
+```sh
+npm run lint
+```

+ 2 - 0
front-end/env.d.ts

@@ -0,0 +1,2 @@
+/// <reference types="vite/client" />
+declare module 'element-plus/dist/locale/zh-cn.mjs'

+ 13 - 0
front-end/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="icon" href="/favicon.ico">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Vite App</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 5266 - 0
front-end/package-lock.json

@@ -0,0 +1,5266 @@
+{
+  "name": "uni-ai-admin",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "uni-ai-admin",
+      "version": "0.0.0",
+      "dependencies": {
+        "@element-plus/icons-vue": "^2.3.1",
+        "axios": "^1.6.4",
+        "dayjs": "^1.11.10",
+        "element-plus": "^2.4.2",
+        "lodash": "^4.17.21",
+        "md-editor-v3": "^4.13.2",
+        "pinia": "^2.1.7",
+        "sse.js": "^2.4.1",
+        "vue": "^3.4.21",
+        "vue-router": "^4.2.5"
+      },
+      "devDependencies": {
+        "@rushstack/eslint-patch": "^1.3.3",
+        "@tsconfig/node18": "^18.2.2",
+        "@types/node": "^18.19.3",
+        "@vitejs/plugin-vue": "^4.5.2",
+        "@vitejs/plugin-vue-jsx": "^3.1.0",
+        "@vue/eslint-config-prettier": "^8.0.0",
+        "@vue/eslint-config-typescript": "^12.0.0",
+        "@vue/tsconfig": "^0.5.0",
+        "adm-zip": "^0.5.10",
+        "eslint": "^8.49.0",
+        "eslint-plugin-vue": "^9.17.0",
+        "fs-extra": "^11.1.1",
+        "npm-run-all2": "^6.1.1",
+        "prettier": "^3.0.3",
+        "sass": "^1.69.7",
+        "typescript": "~5.3.0",
+        "uuid": "^9.0.1",
+        "vite": "^5.0.10",
+        "vue-tsc": "^1.8.25"
+      }
+    },
+    "node_modules/@aashutoshrathi/word-wrap": {
+      "version": "1.2.6",
+      "resolved": "https://registry.npmmirror.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+      "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/@ampproject/remapping": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.1.tgz",
+      "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.0",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/code-frame": {
+      "version": "7.23.5",
+      "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.23.5.tgz",
+      "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/highlight": "^7.23.4",
+        "chalk": "^2.4.2"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/compat-data": {
+      "version": "7.23.5",
+      "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.23.5.tgz",
+      "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core": {
+      "version": "7.23.7",
+      "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.23.7.tgz",
+      "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==",
+      "dev": true,
+      "dependencies": {
+        "@ampproject/remapping": "^2.2.0",
+        "@babel/code-frame": "^7.23.5",
+        "@babel/generator": "^7.23.6",
+        "@babel/helper-compilation-targets": "^7.23.6",
+        "@babel/helper-module-transforms": "^7.23.3",
+        "@babel/helpers": "^7.23.7",
+        "@babel/parser": "^7.23.6",
+        "@babel/template": "^7.22.15",
+        "@babel/traverse": "^7.23.7",
+        "@babel/types": "^7.23.6",
+        "convert-source-map": "^2.0.0",
+        "debug": "^4.1.0",
+        "gensync": "^1.0.0-beta.2",
+        "json5": "^2.2.3",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/generator": {
+      "version": "7.23.6",
+      "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.23.6.tgz",
+      "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.23.6",
+        "@jridgewell/gen-mapping": "^0.3.2",
+        "@jridgewell/trace-mapping": "^0.3.17",
+        "jsesc": "^2.5.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-annotate-as-pure": {
+      "version": "7.22.5",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+      "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.22.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-compilation-targets": {
+      "version": "7.23.6",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz",
+      "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==",
+      "dev": true,
+      "dependencies": {
+        "@babel/compat-data": "^7.23.5",
+        "@babel/helper-validator-option": "^7.23.5",
+        "browserslist": "^4.22.2",
+        "lru-cache": "^5.1.1",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-create-class-features-plugin": {
+      "version": "7.23.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.7.tgz",
+      "integrity": "sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-annotate-as-pure": "^7.22.5",
+        "@babel/helper-environment-visitor": "^7.22.20",
+        "@babel/helper-function-name": "^7.23.0",
+        "@babel/helper-member-expression-to-functions": "^7.23.0",
+        "@babel/helper-optimise-call-expression": "^7.22.5",
+        "@babel/helper-replace-supers": "^7.22.20",
+        "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+        "@babel/helper-split-export-declaration": "^7.22.6",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-environment-visitor": {
+      "version": "7.22.20",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
+      "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-function-name": {
+      "version": "7.23.0",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
+      "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/template": "^7.22.15",
+        "@babel/types": "^7.23.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-hoist-variables": {
+      "version": "7.22.5",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
+      "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.22.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-member-expression-to-functions": {
+      "version": "7.23.0",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz",
+      "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.23.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-imports": {
+      "version": "7.22.15",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
+      "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.22.15"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-transforms": {
+      "version": "7.23.3",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
+      "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-environment-visitor": "^7.22.20",
+        "@babel/helper-module-imports": "^7.22.15",
+        "@babel/helper-simple-access": "^7.22.5",
+        "@babel/helper-split-export-declaration": "^7.22.6",
+        "@babel/helper-validator-identifier": "^7.22.20"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-optimise-call-expression": {
+      "version": "7.22.5",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz",
+      "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.22.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-plugin-utils": {
+      "version": "7.22.5",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+      "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-replace-supers": {
+      "version": "7.22.20",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz",
+      "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-environment-visitor": "^7.22.20",
+        "@babel/helper-member-expression-to-functions": "^7.22.15",
+        "@babel/helper-optimise-call-expression": "^7.22.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-simple-access": {
+      "version": "7.22.5",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
+      "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.22.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+      "version": "7.22.5",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz",
+      "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.22.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-split-export-declaration": {
+      "version": "7.22.6",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
+      "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.22.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.23.4",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
+      "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.22.20",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
+      "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-option": {
+      "version": "7.23.5",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
+      "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helpers": {
+      "version": "7.23.7",
+      "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.23.7.tgz",
+      "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==",
+      "dev": true,
+      "dependencies": {
+        "@babel/template": "^7.22.15",
+        "@babel/traverse": "^7.23.7",
+        "@babel/types": "^7.23.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/highlight": {
+      "version": "7.23.4",
+      "resolved": "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.23.4.tgz",
+      "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.22.20",
+        "chalk": "^2.4.2",
+        "js-tokens": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.24.0",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.24.0.tgz",
+      "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==",
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-syntax-jsx": {
+      "version": "7.23.3",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz",
+      "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.22.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-syntax-typescript": {
+      "version": "7.23.3",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz",
+      "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.22.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-typescript": {
+      "version": "7.23.6",
+      "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz",
+      "integrity": "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-annotate-as-pure": "^7.22.5",
+        "@babel/helper-create-class-features-plugin": "^7.23.6",
+        "@babel/helper-plugin-utils": "^7.22.5",
+        "@babel/plugin-syntax-typescript": "^7.23.3"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/template": {
+      "version": "7.22.15",
+      "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.22.15.tgz",
+      "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.22.13",
+        "@babel/parser": "^7.22.15",
+        "@babel/types": "^7.22.15"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/traverse": {
+      "version": "7.23.7",
+      "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.23.7.tgz",
+      "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.23.5",
+        "@babel/generator": "^7.23.6",
+        "@babel/helper-environment-visitor": "^7.22.20",
+        "@babel/helper-function-name": "^7.23.0",
+        "@babel/helper-hoist-variables": "^7.22.5",
+        "@babel/helper-split-export-declaration": "^7.22.6",
+        "@babel/parser": "^7.23.6",
+        "@babel/types": "^7.23.6",
+        "debug": "^4.3.1",
+        "globals": "^11.1.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.23.6",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.23.6.tgz",
+      "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.23.4",
+        "@babel/helper-validator-identifier": "^7.22.20",
+        "to-fast-properties": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@codemirror/autocomplete": {
+      "version": "6.16.0",
+      "resolved": "https://registry.npmmirror.com/@codemirror/autocomplete/-/autocomplete-6.16.0.tgz",
+      "integrity": "sha512-P/LeCTtZHRTCU4xQsa89vSKWecYv1ZqwzOd5topheGRf+qtacFgBeIMQi3eL8Kt/BUNvxUWkx+5qP2jlGoARrg==",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.17.0",
+        "@lezer/common": "^1.0.0"
+      },
+      "peerDependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/common": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/commands": {
+      "version": "6.4.0",
+      "resolved": "https://registry.npmmirror.com/@codemirror/commands/-/commands-6.4.0.tgz",
+      "integrity": "sha512-HB3utD5GxCvEhSyj5EuG9KpuQQhFpxalh3lwrspyL/GeSNDe4c6JDxVzL12SJ+7gUknHjZzmq7OPCb9QPgiRmQ==",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.4.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/common": "^1.1.0"
+      }
+    },
+    "node_modules/@codemirror/lang-angular": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-angular/-/lang-angular-0.1.3.tgz",
+      "integrity": "sha512-xgeWGJQQl1LyStvndWtruUvb4SnBZDAu/gvFH/ZU+c0W25tQR8e5hq7WTwiIY2dNxnf+49mRiGI/9yxIwB6f5w==",
+      "dependencies": {
+        "@codemirror/lang-html": "^6.0.0",
+        "@codemirror/lang-javascript": "^6.1.2",
+        "@codemirror/language": "^6.0.0",
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.3.3"
+      }
+    },
+    "node_modules/@codemirror/lang-cpp": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-cpp/-/lang-cpp-6.0.2.tgz",
+      "integrity": "sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg==",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@lezer/cpp": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-css": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-css/-/lang-css-6.2.1.tgz",
+      "integrity": "sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.0.2",
+        "@lezer/css": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-go": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-go/-/lang-go-6.0.0.tgz",
+      "integrity": "sha512-mMT4YeYdKGjnffDBOhr1ur1glee4oV/rfMe28vzazNHZkSt7vSiuHiBcgr3L/79Cl2RIjFdpQ1XMD0/T8Rx64g==",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.6.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/go": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-html": {
+      "version": "6.4.9",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
+      "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/lang-css": "^6.0.0",
+        "@codemirror/lang-javascript": "^6.0.0",
+        "@codemirror/language": "^6.4.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.17.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/css": "^1.1.0",
+        "@lezer/html": "^1.3.0"
+      }
+    },
+    "node_modules/@codemirror/lang-java": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-java/-/lang-java-6.0.1.tgz",
+      "integrity": "sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg==",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@lezer/java": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-javascript": {
+      "version": "6.2.2",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
+      "integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.6.0",
+        "@codemirror/lint": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.17.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/javascript": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-json": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
+      "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@lezer/json": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-less": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-less/-/lang-less-6.0.2.tgz",
+      "integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==",
+      "dependencies": {
+        "@codemirror/lang-css": "^6.2.0",
+        "@codemirror/language": "^6.0.0",
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-liquid": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-liquid/-/lang-liquid-6.2.1.tgz",
+      "integrity": "sha512-J1Mratcm6JLNEiX+U2OlCDTysGuwbHD76XwuL5o5bo9soJtSbz2g6RU3vGHFyS5DC8rgVmFSzi7i6oBftm7tnA==",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/lang-html": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.3.1"
+      }
+    },
+    "node_modules/@codemirror/lang-markdown": {
+      "version": "6.2.5",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-markdown/-/lang-markdown-6.2.5.tgz",
+      "integrity": "sha512-Hgke565YcO4fd9pe2uLYxnMufHO5rQwRr+AAhFq8ABuhkrjyX8R5p5s+hZUTdV60O0dMRjxKhBLxz8pu/MkUVA==",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.7.1",
+        "@codemirror/lang-html": "^6.0.0",
+        "@codemirror/language": "^6.3.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/common": "^1.2.1",
+        "@lezer/markdown": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-php": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-php/-/lang-php-6.0.1.tgz",
+      "integrity": "sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA==",
+      "dependencies": {
+        "@codemirror/lang-html": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/php": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-python": {
+      "version": "6.1.5",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-python/-/lang-python-6.1.5.tgz",
+      "integrity": "sha512-hCm+8X6wrnXJCGf+QhmFu1AXkdTVG7dHy0Ly6SI1N3SRPptaMvwX6oNQonOXOMPvmcjiB0xq342KAxX3BYpijw==",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.3.2",
+        "@codemirror/language": "^6.8.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.2.1",
+        "@lezer/python": "^1.1.4"
+      }
+    },
+    "node_modules/@codemirror/lang-rust": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-rust/-/lang-rust-6.0.1.tgz",
+      "integrity": "sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ==",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@lezer/rust": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-sass": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz",
+      "integrity": "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==",
+      "dependencies": {
+        "@codemirror/lang-css": "^6.2.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.0.2",
+        "@lezer/sass": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-sql": {
+      "version": "6.6.3",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-sql/-/lang-sql-6.6.3.tgz",
+      "integrity": "sha512-fo5i3OD/7TmmqMtKycC4OaqfPsRxk0sKOb35g8cOtyUyyI2hfP2qXkDc7Asb6h7BiJK+MU/DYVPnQm6iNB5ZTw==",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-vue": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz",
+      "integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==",
+      "dependencies": {
+        "@codemirror/lang-html": "^6.0.0",
+        "@codemirror/lang-javascript": "^6.1.2",
+        "@codemirror/language": "^6.0.0",
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.3.1"
+      }
+    },
+    "node_modules/@codemirror/lang-wast": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz",
+      "integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-xml": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz",
+      "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.4.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/xml": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-yaml": {
+      "version": "6.1.1",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-yaml/-/lang-yaml-6.1.1.tgz",
+      "integrity": "sha512-HV2NzbK9bbVnjWxwObuZh5FuPCowx51mEfoFT9y3y+M37fA3+pbxx4I7uePuygFzDsAmCTwQSc/kXh/flab4uw==",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.2.0",
+        "@lezer/yaml": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/language": {
+      "version": "6.10.1",
+      "resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.10.1.tgz",
+      "integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==",
+      "dependencies": {
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.23.0",
+        "@lezer/common": "^1.1.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0",
+        "style-mod": "^4.0.0"
+      }
+    },
+    "node_modules/@codemirror/language-data": {
+      "version": "6.5.1",
+      "resolved": "https://registry.npmmirror.com/@codemirror/language-data/-/language-data-6.5.1.tgz",
+      "integrity": "sha512-0sWxeUSNlBr6OmkqybUTImADFUP0M3P0IiSde4nc24bz/6jIYzqYSgkOSLS+CBIoW1vU8Q9KUWXscBXeoMVC9w==",
+      "dependencies": {
+        "@codemirror/lang-angular": "^0.1.0",
+        "@codemirror/lang-cpp": "^6.0.0",
+        "@codemirror/lang-css": "^6.0.0",
+        "@codemirror/lang-go": "^6.0.0",
+        "@codemirror/lang-html": "^6.0.0",
+        "@codemirror/lang-java": "^6.0.0",
+        "@codemirror/lang-javascript": "^6.0.0",
+        "@codemirror/lang-json": "^6.0.0",
+        "@codemirror/lang-less": "^6.0.0",
+        "@codemirror/lang-liquid": "^6.0.0",
+        "@codemirror/lang-markdown": "^6.0.0",
+        "@codemirror/lang-php": "^6.0.0",
+        "@codemirror/lang-python": "^6.0.0",
+        "@codemirror/lang-rust": "^6.0.0",
+        "@codemirror/lang-sass": "^6.0.0",
+        "@codemirror/lang-sql": "^6.0.0",
+        "@codemirror/lang-vue": "^0.1.1",
+        "@codemirror/lang-wast": "^6.0.0",
+        "@codemirror/lang-xml": "^6.0.0",
+        "@codemirror/lang-yaml": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/legacy-modes": "^6.4.0"
+      }
+    },
+    "node_modules/@codemirror/legacy-modes": {
+      "version": "6.4.0",
+      "resolved": "https://registry.npmmirror.com/@codemirror/legacy-modes/-/legacy-modes-6.4.0.tgz",
+      "integrity": "sha512-5m/K+1A6gYR0e+h/dEde7LoGimMjRtWXZFg4Lo70cc8HzjSdHe3fLwjWMR0VRl5KFT1SxalSap7uMgPKF28wBA==",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0"
+      }
+    },
+    "node_modules/@codemirror/lint": {
+      "version": "6.5.0",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lint/-/lint-6.5.0.tgz",
+      "integrity": "sha512-+5YyicIaaAZKU8K43IQi8TBy6mF6giGeWAH7N96Z5LC30Wm5JMjqxOYIE9mxwMG1NbhT2mA3l9hA4uuKUM3E5g==",
+      "dependencies": {
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "crelt": "^1.0.5"
+      }
+    },
+    "node_modules/@codemirror/search": {
+      "version": "6.5.6",
+      "resolved": "https://registry.npmmirror.com/@codemirror/search/-/search-6.5.6.tgz",
+      "integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
+      "dependencies": {
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "crelt": "^1.0.5"
+      }
+    },
+    "node_modules/@codemirror/state": {
+      "version": "6.4.1",
+      "resolved": "https://registry.npmmirror.com/@codemirror/state/-/state-6.4.1.tgz",
+      "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A=="
+    },
+    "node_modules/@codemirror/view": {
+      "version": "6.26.3",
+      "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.26.3.tgz",
+      "integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==",
+      "dependencies": {
+        "@codemirror/state": "^6.4.0",
+        "style-mod": "^4.1.0",
+        "w3c-keyname": "^2.2.4"
+      }
+    },
+    "node_modules/@ctrl/tinycolor": {
+      "version": "3.6.1",
+      "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+      "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@element-plus/icons-vue": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
+      "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz",
+      "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.19.11.tgz",
+      "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz",
+      "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.19.11.tgz",
+      "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz",
+      "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz",
+      "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz",
+      "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz",
+      "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz",
+      "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz",
+      "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz",
+      "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz",
+      "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz",
+      "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz",
+      "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz",
+      "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz",
+      "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz",
+      "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz",
+      "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz",
+      "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz",
+      "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz",
+      "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz",
+      "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz",
+      "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@eslint-community/eslint-utils": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+      "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+      "dev": true,
+      "dependencies": {
+        "eslint-visitor-keys": "^3.3.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+      }
+    },
+    "node_modules/@eslint-community/regexpp": {
+      "version": "4.10.0",
+      "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
+      "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@eslint/eslintrc": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+      "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+      "dev": true,
+      "dependencies": {
+        "ajv": "^6.12.4",
+        "debug": "^4.3.2",
+        "espree": "^9.6.0",
+        "globals": "^13.19.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.2.1",
+        "js-yaml": "^4.1.0",
+        "minimatch": "^3.1.2",
+        "strip-json-comments": "^3.1.1"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/@eslint/eslintrc/node_modules/globals": {
+      "version": "13.24.0",
+      "resolved": "https://registry.npmmirror.com/globals/-/globals-13.24.0.tgz",
+      "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+      "dev": true,
+      "dependencies": {
+        "type-fest": "^0.20.2"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/@eslint/eslintrc/node_modules/type-fest": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz",
+      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@eslint/js": {
+      "version": "8.56.0",
+      "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-8.56.0.tgz",
+      "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@floating-ui/core": {
+      "version": "1.5.2",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.5.2.tgz",
+      "integrity": "sha512-Ii3MrfY/GAIN3OhXNzpCKaLxHQfJF9qvwq/kEJYdqDxeIHa01K8sldugal6TmeeXl+WMvhv9cnVzUTaFFJF09A==",
+      "dependencies": {
+        "@floating-ui/utils": "^0.1.3"
+      }
+    },
+    "node_modules/@floating-ui/dom": {
+      "version": "1.5.3",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.5.3.tgz",
+      "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==",
+      "dependencies": {
+        "@floating-ui/core": "^1.4.2",
+        "@floating-ui/utils": "^0.1.3"
+      }
+    },
+    "node_modules/@floating-ui/utils": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.1.6.tgz",
+      "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
+    },
+    "node_modules/@humanwhocodes/config-array": {
+      "version": "0.11.13",
+      "resolved": "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
+      "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
+      "dev": true,
+      "dependencies": {
+        "@humanwhocodes/object-schema": "^2.0.1",
+        "debug": "^4.1.1",
+        "minimatch": "^3.0.5"
+      },
+      "engines": {
+        "node": ">=10.10.0"
+      }
+    },
+    "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/@humanwhocodes/module-importer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.22"
+      }
+    },
+    "node_modules/@humanwhocodes/object-schema": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
+      "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
+      "dev": true
+    },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
+      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/set-array": "^1.0.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
+      "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/set-array": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.4.15",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.20",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
+      "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/@lezer/common": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/@lezer/common/-/common-1.2.1.tgz",
+      "integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ=="
+    },
+    "node_modules/@lezer/cpp": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/@lezer/cpp/-/cpp-1.1.2.tgz",
+      "integrity": "sha512-macwKtyeUO0EW86r3xWQCzOV9/CF8imJLpJlPv3sDY57cPGeUZ8gXWOWNlJr52TVByMV3PayFQCA5SHEERDmVQ==",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/css": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmmirror.com/@lezer/css/-/css-1.1.8.tgz",
+      "integrity": "sha512-7JhxupKuMBaWQKjQoLtzhGj83DdnZY9MckEOG5+/iLKNK2ZJqKc6hf6uc0HjwCX7Qlok44jBNqZhHKDhEhZYLA==",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/go": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/@lezer/go/-/go-1.0.0.tgz",
+      "integrity": "sha512-co9JfT3QqX1YkrMmourYw2Z8meGC50Ko4d54QEcQbEYpvdUvN4yb0NBZdn/9ertgvjsySxHsKzH3lbm3vqJ4Jw==",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/highlight": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/@lezer/highlight/-/highlight-1.2.0.tgz",
+      "integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==",
+      "dependencies": {
+        "@lezer/common": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/html": {
+      "version": "1.3.9",
+      "resolved": "https://registry.npmmirror.com/@lezer/html/-/html-1.3.9.tgz",
+      "integrity": "sha512-MXxeCMPyrcemSLGaTQEZx0dBUH0i+RPl8RN5GwMAzo53nTsd/Unc/t5ZxACeQoyPUM5/GkPLRUs2WliOImzkRA==",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/java": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/@lezer/java/-/java-1.1.1.tgz",
+      "integrity": "sha512-mt3dX13fRlpY7RlWELYRakanXgmwXsLRCrhstrn+c1sZd7jR2xle46/3heoxGd+oHxnuTnpoyXTyxcLJQs9+mQ==",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/javascript": {
+      "version": "1.4.14",
+      "resolved": "https://registry.npmmirror.com/@lezer/javascript/-/javascript-1.4.14.tgz",
+      "integrity": "sha512-GEdUyspTRgc5dwIGebUk+f3BekvqEWVIYsIuAC3pA8e8wcikGwBZRWRa450L0s8noGWuULwnmi4yjxTnYz9PpA==",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.1.3",
+        "@lezer/lr": "^1.3.0"
+      }
+    },
+    "node_modules/@lezer/json": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/@lezer/json/-/json-1.0.2.tgz",
+      "integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/lr": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/@lezer/lr/-/lr-1.4.0.tgz",
+      "integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
+      "dependencies": {
+        "@lezer/common": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/markdown": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/@lezer/markdown/-/markdown-1.3.0.tgz",
+      "integrity": "sha512-ErbEQ15eowmJUyT095e9NJc3BI9yZ894fjSDtHftD0InkfUBGgnKSU6dvan9jqsZuNHg2+ag/1oyDRxNsENupQ==",
+      "dependencies": {
+        "@lezer/common": "^1.0.0",
+        "@lezer/highlight": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/php": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/@lezer/php/-/php-1.0.2.tgz",
+      "integrity": "sha512-GN7BnqtGRpFyeoKSEqxvGvhJQiI4zkgmYnDk/JIyc7H7Ifc1tkPnUn/R2R8meH3h/aBf5rzjvU8ZQoyiNDtDrA==",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.1.0"
+      }
+    },
+    "node_modules/@lezer/python": {
+      "version": "1.1.13",
+      "resolved": "https://registry.npmmirror.com/@lezer/python/-/python-1.1.13.tgz",
+      "integrity": "sha512-AdbRAtdQq94PfTNd4kqMEJhH2fqa2JdoyyqqVewY6w34w2Gi6dg2JuOtOgR21Bi0zP9r0KjSSHOUq/tP7FVT8A==",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/rust": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/@lezer/rust/-/rust-1.0.2.tgz",
+      "integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/sass": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/@lezer/sass/-/sass-1.0.6.tgz",
+      "integrity": "sha512-w/RCO2dIzZH1To8p+xjs8cE+yfgGus8NZ/dXeWl/QzHyr+TeBs71qiE70KPImEwvTsmEjoWh0A5SxMzKd5BWBQ==",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/xml": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/@lezer/xml/-/xml-1.0.5.tgz",
+      "integrity": "sha512-VFouqOzmUWfIg+tfmpcdV33ewtK+NSwd4ngSe1aG7HFb4BN0ExyY1b8msp+ndFrnlG4V4iC8yXacjFtrwERnaw==",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/yaml": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/@lezer/yaml/-/yaml-1.0.2.tgz",
+      "integrity": "sha512-XCkwuxe+eumJ28nA9e1S6XKsXz9W7V/AG+WBiWOtiIuUpKcZ/bHuvN8bLxSDREIcybSRpEd/jvphh4vgm6Ed2g==",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.4.0"
+      }
+    },
+    "node_modules/@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@pkgr/core": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/@pkgr/core/-/core-0.1.0.tgz",
+      "integrity": "sha512-Zwq5OCzuwJC2jwqmpEQt7Ds1DTi6BWSwoGkbb1n9pO3hzb35BoJELx7c0T23iDkBGkh2e7tvOtjF3tr3OaQHDQ==",
+      "dev": true,
+      "engines": {
+        "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@popperjs/core": {
+      "name": "@sxzz/popperjs-es",
+      "version": "2.11.7",
+      "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+      "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ=="
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.3.tgz",
+      "integrity": "sha512-nvh9bB41vXEoKKvlWCGptpGt8EhrEwPQFDCY0VAto+R+qpSbaErPS3OjMZuXR8i/2UVw952Dtlnl2JFxH31Qvg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.3.tgz",
+      "integrity": "sha512-kffYCJ2RhDL1DlshLzYPyJtVeusHlA8Q1j6k6s4AEVKLq/3HfGa2ADDycLsmPo3OW83r4XtOPqRMbcFzFsEIzQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.3.tgz",
+      "integrity": "sha512-Fo7DR6Q9/+ztTyMBZ79+WJtb8RWZonyCgkBCjV51rW5K/dizBzImTW6HLC0pzmHaAevwM0jW1GtB5LCFE81mSw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.3.tgz",
+      "integrity": "sha512-5HcxDF9fqHucIlTiw/gmMb3Qv23L8bLCg904I74Q2lpl4j/20z9ogaD3tWkeguRuz+/17cuS321PT3PAuyjQdg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.3.tgz",
+      "integrity": "sha512-cO6hKV+99D1V7uNJQn1chWaF9EGp7qV2N8sGH99q9Y62bsbN6Il55EwJppEWT+JiqDRg396vWCgwdHwje8itBQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.3.tgz",
+      "integrity": "sha512-xANyq6lVg6KMO8UUs0LjA4q7di3tPpDbzLPgVEU2/F1ngIZ54eli8Zdt3uUUTMXVbgTCafIO+JPeGMhu097i3w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.3.tgz",
+      "integrity": "sha512-TZJUfRTugVFATQToCMD8DNV6jv/KpSwhE1lLq5kXiQbBX3Pqw6dRKtzNkh5wcp0n09reBBq/7CGDERRw9KmE+g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.3.tgz",
+      "integrity": "sha512-4/QVaRyaB5tkEAGfjVvWrmWdPF6F2NoaoO5uEP7N0AyeBw7l8SeCWWKAGrbx/00PUdHrJVURJiYikazslSKttQ==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.3.tgz",
+      "integrity": "sha512-koLC6D3pj1YLZSkTy/jsk3HOadp7q2h6VQl/lPX854twOmmLNekHB6yuS+MkWcKdGGdW1JPuPBv/ZYhr5Yhtdg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.3.tgz",
+      "integrity": "sha512-0OAkQ4HBp+JO2ip2Lgt/ShlrveOMzyhwt2D0KvqH28jFPqfZco28KSq76zymZwmU+F6GRojdxtQMJiNSXKNzeA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.3.tgz",
+      "integrity": "sha512-z5uvoMvdRWggigOnsb9OOCLERHV0ykRZoRB5O+URPZC9zM3pkoMg5fN4NKu2oHqgkzZtfx9u4njqqlYEzM1v9A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.3.tgz",
+      "integrity": "sha512-wxomCHjBVKws+O4N1WLnniKCXu7vkLtdq9Fl9CN/EbwEldojvUrkoHE/fBLZzC7IT/x12Ut6d6cRs4dFvqJkMg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.3.tgz",
+      "integrity": "sha512-1Qf/qk/iEtx0aOi+AQQt5PBoW0mFngsm7bPuxHClC/hWh2hHBktR6ktSfUg5b5rC9v8hTwNmHE7lBWXkgqluUQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rushstack/eslint-patch": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmmirror.com/@rushstack/eslint-patch/-/eslint-patch-1.6.1.tgz",
+      "integrity": "sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node18": {
+      "version": "18.2.2",
+      "resolved": "https://registry.npmmirror.com/@tsconfig/node18/-/node18-18.2.2.tgz",
+      "integrity": "sha512-d6McJeGsuoRlwWZmVIeE8CUA27lu6jLjvv1JzqmpsytOYYbVi1tHZEnwCNVOXnj4pyLvneZlFlpXUK+X9wBWyw==",
+      "dev": true
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz",
+      "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+      "dev": true
+    },
+    "node_modules/@types/json-schema": {
+      "version": "7.0.15",
+      "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz",
+      "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+      "dev": true
+    },
+    "node_modules/@types/linkify-it": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmmirror.com/@types/linkify-it/-/linkify-it-3.0.5.tgz",
+      "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw=="
+    },
+    "node_modules/@types/lodash": {
+      "version": "4.14.202",
+      "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.202.tgz",
+      "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ=="
+    },
+    "node_modules/@types/lodash-es": {
+      "version": "4.17.12",
+      "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+      "dependencies": {
+        "@types/lodash": "*"
+      }
+    },
+    "node_modules/@types/markdown-it": {
+      "version": "13.0.7",
+      "resolved": "https://registry.npmmirror.com/@types/markdown-it/-/markdown-it-13.0.7.tgz",
+      "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==",
+      "dependencies": {
+        "@types/linkify-it": "*",
+        "@types/mdurl": "*"
+      }
+    },
+    "node_modules/@types/mdurl": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/@types/mdurl/-/mdurl-1.0.5.tgz",
+      "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA=="
+    },
+    "node_modules/@types/node": {
+      "version": "18.19.4",
+      "resolved": "https://registry.npmmirror.com/@types/node/-/node-18.19.4.tgz",
+      "integrity": "sha512-xNzlUhzoHotIsnFoXmJB+yWmBvFZgKCI9TtPIEdYIMM1KWfwuY8zh7wvc1u1OAXlC7dlf6mZVx/s+Y5KfFz19A==",
+      "dev": true,
+      "dependencies": {
+        "undici-types": "~5.26.4"
+      }
+    },
+    "node_modules/@types/normalize-package-data": {
+      "version": "2.4.4",
+      "resolved": "https://registry.npmmirror.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
+      "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
+      "dev": true
+    },
+    "node_modules/@types/semver": {
+      "version": "7.5.6",
+      "resolved": "https://registry.npmmirror.com/@types/semver/-/semver-7.5.6.tgz",
+      "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
+      "dev": true
+    },
+    "node_modules/@types/web-bluetooth": {
+      "version": "0.0.16",
+      "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
+      "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ=="
+    },
+    "node_modules/@typescript-eslint/eslint-plugin": {
+      "version": "6.17.0",
+      "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz",
+      "integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/regexpp": "^4.5.1",
+        "@typescript-eslint/scope-manager": "6.17.0",
+        "@typescript-eslint/type-utils": "6.17.0",
+        "@typescript-eslint/utils": "6.17.0",
+        "@typescript-eslint/visitor-keys": "6.17.0",
+        "debug": "^4.3.4",
+        "graphemer": "^1.4.0",
+        "ignore": "^5.2.4",
+        "natural-compare": "^1.4.0",
+        "semver": "^7.5.4",
+        "ts-api-utils": "^1.0.1"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "peerDependencies": {
+        "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
+        "eslint": "^7.0.0 || ^8.0.0"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
+      "version": "7.5.4",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz",
+      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/parser": {
+      "version": "6.17.0",
+      "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-6.17.0.tgz",
+      "integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/scope-manager": "6.17.0",
+        "@typescript-eslint/types": "6.17.0",
+        "@typescript-eslint/typescript-estree": "6.17.0",
+        "@typescript-eslint/visitor-keys": "6.17.0",
+        "debug": "^4.3.4"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "peerDependencies": {
+        "eslint": "^7.0.0 || ^8.0.0"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/scope-manager": {
+      "version": "6.17.0",
+      "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz",
+      "integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "6.17.0",
+        "@typescript-eslint/visitor-keys": "6.17.0"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils": {
+      "version": "6.17.0",
+      "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz",
+      "integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/typescript-estree": "6.17.0",
+        "@typescript-eslint/utils": "6.17.0",
+        "debug": "^4.3.4",
+        "ts-api-utils": "^1.0.1"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "peerDependencies": {
+        "eslint": "^7.0.0 || ^8.0.0"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/types": {
+      "version": "6.17.0",
+      "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-6.17.0.tgz",
+      "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==",
+      "dev": true,
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree": {
+      "version": "6.17.0",
+      "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz",
+      "integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "6.17.0",
+        "@typescript-eslint/visitor-keys": "6.17.0",
+        "debug": "^4.3.4",
+        "globby": "^11.1.0",
+        "is-glob": "^4.0.3",
+        "minimatch": "9.0.3",
+        "semver": "^7.5.4",
+        "ts-api-utils": "^1.0.1"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+      "version": "7.5.4",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz",
+      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/utils": {
+      "version": "6.17.0",
+      "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-6.17.0.tgz",
+      "integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.4.0",
+        "@types/json-schema": "^7.0.12",
+        "@types/semver": "^7.5.0",
+        "@typescript-eslint/scope-manager": "6.17.0",
+        "@typescript-eslint/types": "6.17.0",
+        "@typescript-eslint/typescript-estree": "6.17.0",
+        "semver": "^7.5.4"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "peerDependencies": {
+        "eslint": "^7.0.0 || ^8.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/utils/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@typescript-eslint/utils/node_modules/semver": {
+      "version": "7.5.4",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz",
+      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@typescript-eslint/utils/node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/visitor-keys": {
+      "version": "6.17.0",
+      "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz",
+      "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "6.17.0",
+        "eslint-visitor-keys": "^3.4.1"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      }
+    },
+    "node_modules/@ungap/structured-clone": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+      "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+      "dev": true
+    },
+    "node_modules/@vavt/util": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmmirror.com/@vavt/util/-/util-1.5.1.tgz",
+      "integrity": "sha512-/q/ilzRwZZJlnDAl6DGZ8pinOSAjR91dcck79bi1ujrqYlPaFVHnbmkDeGPuLub6A821rXvtPXVRXULrfMN03Q=="
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz",
+      "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==",
+      "dev": true,
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^4.0.0 || ^5.0.0",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@vitejs/plugin-vue-jsx": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-3.1.0.tgz",
+      "integrity": "sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/core": "^7.23.3",
+        "@babel/plugin-transform-typescript": "^7.23.3",
+        "@vue/babel-plugin-jsx": "^1.1.5"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^4.0.0 || ^5.0.0",
+        "vue": "^3.0.0"
+      }
+    },
+    "node_modules/@volar/language-core": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-1.11.1.tgz",
+      "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==",
+      "dev": true,
+      "dependencies": {
+        "@volar/source-map": "1.11.1"
+      }
+    },
+    "node_modules/@volar/source-map": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-1.11.1.tgz",
+      "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==",
+      "dev": true,
+      "dependencies": {
+        "muggle-string": "^0.3.1"
+      }
+    },
+    "node_modules/@volar/typescript": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-1.11.1.tgz",
+      "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==",
+      "dev": true,
+      "dependencies": {
+        "@volar/language-core": "1.11.1",
+        "path-browserify": "^1.0.1"
+      }
+    },
+    "node_modules/@vue/babel-helper-vue-transform-on": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.1.5.tgz",
+      "integrity": "sha512-SgUymFpMoAyWeYWLAY+MkCK3QEROsiUnfaw5zxOVD/M64KQs8D/4oK6Q5omVA2hnvEOE0SCkH2TZxs/jnnUj7w==",
+      "dev": true
+    },
+    "node_modules/@vue/babel-plugin-jsx": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.1.5.tgz",
+      "integrity": "sha512-nKs1/Bg9U1n3qSWnsHhCVQtAzI6aQXqua8j/bZrau8ywT1ilXQbK4FwEJGmU8fV7tcpuFvWmmN7TMmV1OBma1g==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.22.5",
+        "@babel/plugin-syntax-jsx": "^7.22.5",
+        "@babel/template": "^7.22.5",
+        "@babel/traverse": "^7.22.5",
+        "@babel/types": "^7.22.5",
+        "@vue/babel-helper-vue-transform-on": "^1.1.5",
+        "camelcase": "^6.3.0",
+        "html-tags": "^3.3.1",
+        "svg-tags": "^1.0.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.4.21.tgz",
+      "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==",
+      "dependencies": {
+        "@babel/parser": "^7.23.9",
+        "@vue/shared": "3.4.21",
+        "entities": "^4.5.0",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.0.2"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz",
+      "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==",
+      "dependencies": {
+        "@vue/compiler-core": "3.4.21",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz",
+      "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==",
+      "dependencies": {
+        "@babel/parser": "^7.23.9",
+        "@vue/compiler-core": "3.4.21",
+        "@vue/compiler-dom": "3.4.21",
+        "@vue/compiler-ssr": "3.4.21",
+        "@vue/shared": "3.4.21",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.7",
+        "postcss": "^8.4.35",
+        "source-map-js": "^1.0.2"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz",
+      "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.4.21",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@vue/devtools-api": {
+      "version": "6.5.1",
+      "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.1.tgz",
+      "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA=="
+    },
+    "node_modules/@vue/eslint-config-prettier": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmmirror.com/@vue/eslint-config-prettier/-/eslint-config-prettier-8.0.0.tgz",
+      "integrity": "sha512-55dPqtC4PM/yBjhAr+yEw6+7KzzdkBuLmnhBrDfp4I48+wy+Giqqj9yUr5T2uD/BkBROjjmqnLZmXRdOx/VtQg==",
+      "dev": true,
+      "dependencies": {
+        "eslint-config-prettier": "^8.8.0",
+        "eslint-plugin-prettier": "^5.0.0"
+      },
+      "peerDependencies": {
+        "eslint": ">= 8.0.0",
+        "prettier": ">= 3.0.0"
+      }
+    },
+    "node_modules/@vue/eslint-config-typescript": {
+      "version": "12.0.0",
+      "resolved": "https://registry.npmmirror.com/@vue/eslint-config-typescript/-/eslint-config-typescript-12.0.0.tgz",
+      "integrity": "sha512-StxLFet2Qe97T8+7L8pGlhYBBr8Eg05LPuTDVopQV6il+SK6qqom59BA/rcFipUef2jD8P2X44Vd8tMFytfvlg==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/eslint-plugin": "^6.7.0",
+        "@typescript-eslint/parser": "^6.7.0",
+        "vue-eslint-parser": "^9.3.1"
+      },
+      "engines": {
+        "node": "^14.17.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0",
+        "eslint-plugin-vue": "^9.0.0",
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vue/language-core": {
+      "version": "1.8.27",
+      "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-1.8.27.tgz",
+      "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==",
+      "dev": true,
+      "dependencies": {
+        "@volar/language-core": "~1.11.1",
+        "@volar/source-map": "~1.11.1",
+        "@vue/compiler-dom": "^3.3.0",
+        "@vue/shared": "^3.3.0",
+        "computeds": "^0.0.1",
+        "minimatch": "^9.0.3",
+        "muggle-string": "^0.3.1",
+        "path-browserify": "^1.0.1",
+        "vue-template-compiler": "^2.7.14"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.4.21.tgz",
+      "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==",
+      "dependencies": {
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.4.21.tgz",
+      "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==",
+      "dependencies": {
+        "@vue/reactivity": "3.4.21",
+        "@vue/shared": "3.4.21"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz",
+      "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==",
+      "dependencies": {
+        "@vue/runtime-core": "3.4.21",
+        "@vue/shared": "3.4.21",
+        "csstype": "^3.1.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.4.21.tgz",
+      "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.4.21",
+        "@vue/shared": "3.4.21"
+      },
+      "peerDependencies": {
+        "vue": "3.4.21"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.4.21.tgz",
+      "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
+    },
+    "node_modules/@vue/tsconfig": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmmirror.com/@vue/tsconfig/-/tsconfig-0.5.1.tgz",
+      "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==",
+      "dev": true
+    },
+    "node_modules/@vueuse/core": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz",
+      "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.16",
+        "@vueuse/metadata": "9.13.0",
+        "@vueuse/shared": "9.13.0",
+        "vue-demi": "*"
+      }
+    },
+    "node_modules/@vueuse/core/node_modules/vue-demi": {
+      "version": "0.14.6",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz",
+      "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vueuse/metadata": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
+      "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ=="
+    },
+    "node_modules/@vueuse/shared": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz",
+      "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
+      "dependencies": {
+        "vue-demi": "*"
+      }
+    },
+    "node_modules/@vueuse/shared/node_modules/vue-demi": {
+      "version": "0.14.6",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz",
+      "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/acorn": {
+      "version": "8.11.3",
+      "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.11.3.tgz",
+      "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+      "dev": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/acorn-jsx": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true,
+      "peerDependencies": {
+        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+      }
+    },
+    "node_modules/adm-zip": {
+      "version": "0.5.10",
+      "resolved": "https://registry.npmmirror.com/adm-zip/-/adm-zip-0.5.10.tgz",
+      "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.0"
+      }
+    },
+    "node_modules/ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^1.9.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "dev": true,
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/argparse": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+    },
+    "node_modules/array-union": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz",
+      "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/async-validator": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "node_modules/axios": {
+      "version": "1.6.4",
+      "resolved": "https://registry.npmmirror.com/axios/-/axios-1.6.4.tgz",
+      "integrity": "sha512-heJnIs6N4aa1eSthhN9M5ioILu8Wi8vmQW9iHQ9NUvfkJb0lEEDUiIdQNAuBtfUt3FxReaKdpQA5DbmMOqzF/A==",
+      "dependencies": {
+        "follow-redirects": "^1.15.4",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz",
+      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/boolbase": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz",
+      "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+      "dev": true
+    },
+    "node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dev": true,
+      "dependencies": {
+        "fill-range": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/browserslist": {
+      "version": "4.22.2",
+      "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.22.2.tgz",
+      "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==",
+      "dev": true,
+      "dependencies": {
+        "caniuse-lite": "^1.0.30001565",
+        "electron-to-chromium": "^1.4.601",
+        "node-releases": "^2.0.14",
+        "update-browserslist-db": "^1.0.13"
+      },
+      "bin": {
+        "browserslist": "cli.js"
+      },
+      "engines": {
+        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+      }
+    },
+    "node_modules/callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/camelcase": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz",
+      "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/caniuse-lite": {
+      "version": "1.0.30001574",
+      "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001574.tgz",
+      "integrity": "sha512-BtYEK4r/iHt/txm81KBudCUcTy7t+s9emrIaHqjYurQ10x71zJ5VQ9x1dYPcz/b+pKSp4y/v1xSI67A+LzpNyg==",
+      "dev": true
+    },
+    "node_modules/chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz",
+      "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+      "dev": true,
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/chokidar/node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/codemirror": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.1.tgz",
+      "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/commands": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/lint": "^6.0.0",
+        "@codemirror/search": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "1.1.3"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+      "dev": true
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+    },
+    "node_modules/computeds": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmmirror.com/computeds/-/computeds-0.0.1.tgz",
+      "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==",
+      "dev": true
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "dev": true
+    },
+    "node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "dev": true
+    },
+    "node_modules/copy-to-clipboard": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
+      "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
+      "dependencies": {
+        "toggle-selection": "^1.0.6"
+      }
+    },
+    "node_modules/crelt": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz",
+      "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
+    },
+    "node_modules/cross-spawn": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
+      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+      "dev": true,
+      "dependencies": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/cssesc": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
+      "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+      "dev": true,
+      "bin": {
+        "cssesc": "bin/cssesc"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/cssfilter": {
+      "version": "0.0.10",
+      "resolved": "https://registry.npmmirror.com/cssfilter/-/cssfilter-0.0.10.tgz",
+      "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="
+    },
+    "node_modules/csstype": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+    },
+    "node_modules/dayjs": {
+      "version": "1.11.10",
+      "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.10.tgz",
+      "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
+    },
+    "node_modules/de-indent": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz",
+      "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
+      "dev": true
+    },
+    "node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/deep-is": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": true
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/dir-glob": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz",
+      "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+      "dev": true,
+      "dependencies": {
+        "path-type": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/doctrine": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz",
+      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+      "dev": true,
+      "dependencies": {
+        "esutils": "^2.0.2"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/electron-to-chromium": {
+      "version": "1.4.622",
+      "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.622.tgz",
+      "integrity": "sha512-GZ47DEy0Gm2Z8RVG092CkFvX7SdotG57c4YZOe8W8qD4rOmk3plgeNmiLVRHP/Liqj1wRiY3uUUod9vb9hnxZA==",
+      "dev": true
+    },
+    "node_modules/element-plus": {
+      "version": "2.4.4",
+      "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.4.4.tgz",
+      "integrity": "sha512-TlKubXJgxwhER0dw+8ULn9hr9kZjraV4R6Q/eidwWUwCKxwXYPBGmMKsZ/85tlxlhMYbcLZd/YZh6G3QkHX4fg==",
+      "dependencies": {
+        "@ctrl/tinycolor": "^3.4.1",
+        "@element-plus/icons-vue": "^2.3.1",
+        "@floating-ui/dom": "^1.0.1",
+        "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+        "@types/lodash": "^4.14.182",
+        "@types/lodash-es": "^4.17.6",
+        "@vueuse/core": "^9.1.0",
+        "async-validator": "^4.2.5",
+        "dayjs": "^1.11.3",
+        "escape-html": "^1.0.3",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "lodash-unified": "^1.0.2",
+        "memoize-one": "^6.0.0",
+        "normalize-wheel-es": "^1.2.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "engines": {
+        "node": ">=0.12"
+      }
+    },
+    "node_modules/error-ex": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz",
+      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+      "dev": true,
+      "dependencies": {
+        "is-arrayish": "^0.2.1"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.19.11",
+      "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.19.11.tgz",
+      "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.19.11",
+        "@esbuild/android-arm": "0.19.11",
+        "@esbuild/android-arm64": "0.19.11",
+        "@esbuild/android-x64": "0.19.11",
+        "@esbuild/darwin-arm64": "0.19.11",
+        "@esbuild/darwin-x64": "0.19.11",
+        "@esbuild/freebsd-arm64": "0.19.11",
+        "@esbuild/freebsd-x64": "0.19.11",
+        "@esbuild/linux-arm": "0.19.11",
+        "@esbuild/linux-arm64": "0.19.11",
+        "@esbuild/linux-ia32": "0.19.11",
+        "@esbuild/linux-loong64": "0.19.11",
+        "@esbuild/linux-mips64el": "0.19.11",
+        "@esbuild/linux-ppc64": "0.19.11",
+        "@esbuild/linux-riscv64": "0.19.11",
+        "@esbuild/linux-s390x": "0.19.11",
+        "@esbuild/linux-x64": "0.19.11",
+        "@esbuild/netbsd-x64": "0.19.11",
+        "@esbuild/openbsd-x64": "0.19.11",
+        "@esbuild/sunos-x64": "0.19.11",
+        "@esbuild/win32-arm64": "0.19.11",
+        "@esbuild/win32-ia32": "0.19.11",
+        "@esbuild/win32-x64": "0.19.11"
+      }
+    },
+    "node_modules/escalade": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz",
+      "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/eslint": {
+      "version": "8.56.0",
+      "resolved": "https://registry.npmmirror.com/eslint/-/eslint-8.56.0.tgz",
+      "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.2.0",
+        "@eslint-community/regexpp": "^4.6.1",
+        "@eslint/eslintrc": "^2.1.4",
+        "@eslint/js": "8.56.0",
+        "@humanwhocodes/config-array": "^0.11.13",
+        "@humanwhocodes/module-importer": "^1.0.1",
+        "@nodelib/fs.walk": "^1.2.8",
+        "@ungap/structured-clone": "^1.2.0",
+        "ajv": "^6.12.4",
+        "chalk": "^4.0.0",
+        "cross-spawn": "^7.0.2",
+        "debug": "^4.3.2",
+        "doctrine": "^3.0.0",
+        "escape-string-regexp": "^4.0.0",
+        "eslint-scope": "^7.2.2",
+        "eslint-visitor-keys": "^3.4.3",
+        "espree": "^9.6.1",
+        "esquery": "^1.4.2",
+        "esutils": "^2.0.2",
+        "fast-deep-equal": "^3.1.3",
+        "file-entry-cache": "^6.0.1",
+        "find-up": "^5.0.0",
+        "glob-parent": "^6.0.2",
+        "globals": "^13.19.0",
+        "graphemer": "^1.4.0",
+        "ignore": "^5.2.0",
+        "imurmurhash": "^0.1.4",
+        "is-glob": "^4.0.0",
+        "is-path-inside": "^3.0.3",
+        "js-yaml": "^4.1.0",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "levn": "^0.4.1",
+        "lodash.merge": "^4.6.2",
+        "minimatch": "^3.1.2",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.9.3",
+        "strip-ansi": "^6.0.1",
+        "text-table": "^0.2.0"
+      },
+      "bin": {
+        "eslint": "bin/eslint.js"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/eslint-config-prettier": {
+      "version": "8.10.0",
+      "resolved": "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz",
+      "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==",
+      "dev": true,
+      "bin": {
+        "eslint-config-prettier": "bin/cli.js"
+      },
+      "peerDependencies": {
+        "eslint": ">=7.0.0"
+      }
+    },
+    "node_modules/eslint-plugin-prettier": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.2.tgz",
+      "integrity": "sha512-dhlpWc9vOwohcWmClFcA+HjlvUpuyynYs0Rf+L/P6/0iQE6vlHW9l5bkfzN62/Stm9fbq8ku46qzde76T1xlSg==",
+      "dev": true,
+      "dependencies": {
+        "prettier-linter-helpers": "^1.0.0",
+        "synckit": "^0.8.6"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "@types/eslint": ">=8.0.0",
+        "eslint": ">=8.0.0",
+        "eslint-config-prettier": "*",
+        "prettier": ">=3.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/eslint": {
+          "optional": true
+        },
+        "eslint-config-prettier": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/eslint-plugin-vue": {
+      "version": "9.19.2",
+      "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-9.19.2.tgz",
+      "integrity": "sha512-CPDqTOG2K4Ni2o4J5wixkLVNwgctKXFu6oBpVJlpNq7f38lh9I80pRTouZSJ2MAebPJlINU/KTFSXyQfBUlymA==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.4.0",
+        "natural-compare": "^1.4.0",
+        "nth-check": "^2.1.1",
+        "postcss-selector-parser": "^6.0.13",
+        "semver": "^7.5.4",
+        "vue-eslint-parser": "^9.3.1",
+        "xml-name-validator": "^4.0.0"
+      },
+      "engines": {
+        "node": "^14.17.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0"
+      }
+    },
+    "node_modules/eslint-plugin-vue/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/eslint-plugin-vue/node_modules/semver": {
+      "version": "7.5.4",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz",
+      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/eslint-plugin-vue/node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    },
+    "node_modules/eslint-scope": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.2.2.tgz",
+      "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+      "dev": true,
+      "dependencies": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/eslint-visitor-keys": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/eslint/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/eslint/node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/eslint/node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/eslint/node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/eslint/node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "node_modules/eslint/node_modules/escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/eslint/node_modules/globals": {
+      "version": "13.24.0",
+      "resolved": "https://registry.npmmirror.com/globals/-/globals-13.24.0.tgz",
+      "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+      "dev": true,
+      "dependencies": {
+        "type-fest": "^0.20.2"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/eslint/node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/eslint/node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/eslint/node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/eslint/node_modules/type-fest": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz",
+      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/espree": {
+      "version": "9.6.1",
+      "resolved": "https://registry.npmmirror.com/espree/-/espree-9.6.1.tgz",
+      "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^8.9.0",
+        "acorn-jsx": "^5.3.2",
+        "eslint-visitor-keys": "^3.4.1"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/esquery": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.5.0.tgz",
+      "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.1.0"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "node_modules/esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "node_modules/fast-diff": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.3.0.tgz",
+      "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+      "dev": true
+    },
+    "node_modules/fast-glob": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.2.tgz",
+      "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.4"
+      },
+      "engines": {
+        "node": ">=8.6.0"
+      }
+    },
+    "node_modules/fast-glob/node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "node_modules/fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true
+    },
+    "node_modules/fastq": {
+      "version": "1.16.0",
+      "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.16.0.tgz",
+      "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==",
+      "dev": true,
+      "dependencies": {
+        "reusify": "^1.0.4"
+      }
+    },
+    "node_modules/file-entry-cache": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+      "dev": true,
+      "dependencies": {
+        "flat-cache": "^3.0.4"
+      },
+      "engines": {
+        "node": "^10.12.0 || >=12.0.0"
+      }
+    },
+    "node_modules/fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dev": true,
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/find-up": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz",
+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "dev": true,
+      "dependencies": {
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/flat-cache": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-3.2.0.tgz",
+      "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+      "dev": true,
+      "dependencies": {
+        "flatted": "^3.2.9",
+        "keyv": "^4.5.3",
+        "rimraf": "^3.0.2"
+      },
+      "engines": {
+        "node": "^10.12.0 || >=12.0.0"
+      }
+    },
+    "node_modules/flatted": {
+      "version": "3.2.9",
+      "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.2.9.tgz",
+      "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
+      "dev": true
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.4",
+      "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.4.tgz",
+      "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fs-extra": {
+      "version": "11.2.0",
+      "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.2.0.tgz",
+      "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
+      "dev": true,
+      "dependencies": {
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^6.0.1",
+        "universalify": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=14.14"
+      }
+    },
+    "node_modules/fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+      "dev": true
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "dev": true
+    },
+    "node_modules/gensync": {
+      "version": "1.0.0-beta.2",
+      "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz",
+      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/glob": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
+      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+      "dev": true,
+      "dependencies": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.1.1",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz",
+      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/glob/node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/glob/node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/globals": {
+      "version": "11.12.0",
+      "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz",
+      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/globby": {
+      "version": "11.1.0",
+      "resolved": "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz",
+      "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+      "dev": true,
+      "dependencies": {
+        "array-union": "^2.1.0",
+        "dir-glob": "^3.0.1",
+        "fast-glob": "^3.2.9",
+        "ignore": "^5.2.0",
+        "merge2": "^1.4.1",
+        "slash": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/graceful-fs": {
+      "version": "4.2.11",
+      "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+      "dev": true
+    },
+    "node_modules/graphemer": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz",
+      "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+      "dev": true
+    },
+    "node_modules/has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "dev": true,
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+      "dev": true,
+      "bin": {
+        "he": "bin/he"
+      }
+    },
+    "node_modules/hosted-git-info": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-7.0.1.tgz",
+      "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^10.0.1"
+      },
+      "engines": {
+        "node": "^16.14.0 || >=18.0.0"
+      }
+    },
+    "node_modules/hosted-git-info/node_modules/lru-cache": {
+      "version": "10.1.0",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.1.0.tgz",
+      "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==",
+      "dev": true,
+      "engines": {
+        "node": "14 || >=16.14"
+      }
+    },
+    "node_modules/html-tags": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmmirror.com/html-tags/-/html-tags-3.3.1.tgz",
+      "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ignore": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.0.tgz",
+      "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/immutable": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.4.tgz",
+      "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==",
+      "dev": true
+    },
+    "node_modules/import-fresh": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz",
+      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+      "dev": true,
+      "dependencies": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.19"
+      }
+    },
+    "node_modules/inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+      "dev": true,
+      "dependencies": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true
+    },
+    "node_modules/is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+      "dev": true
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-core-module": {
+      "version": "2.13.1",
+      "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.13.1.tgz",
+      "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
+      "dev": true,
+      "dependencies": {
+        "hasown": "^2.0.0"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/is-path-inside": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz",
+      "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true
+    },
+    "node_modules/js-yaml": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz",
+      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
+      "dependencies": {
+        "argparse": "^2.0.1"
+      },
+      "bin": {
+        "js-yaml": "bin/js-yaml.js"
+      }
+    },
+    "node_modules/jsesc": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz",
+      "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+      "dev": true,
+      "bin": {
+        "jsesc": "bin/jsesc"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/json-buffer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz",
+      "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+      "dev": true
+    },
+    "node_modules/json-parse-even-better-errors": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz",
+      "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==",
+      "dev": true,
+      "engines": {
+        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+      }
+    },
+    "node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "node_modules/json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true
+    },
+    "node_modules/json5": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+      "dev": true,
+      "bin": {
+        "json5": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/jsonfile": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz",
+      "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+      "dev": true,
+      "dependencies": {
+        "universalify": "^2.0.0"
+      },
+      "optionalDependencies": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
+    "node_modules/keyv": {
+      "version": "4.5.4",
+      "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
+      "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+      "dev": true,
+      "dependencies": {
+        "json-buffer": "3.0.1"
+      }
+    },
+    "node_modules/levn": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz",
+      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1",
+        "type-check": "~0.4.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/lines-and-columns": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-2.0.4.tgz",
+      "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==",
+      "dev": true,
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      }
+    },
+    "node_modules/linkify-it": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-5.0.0.tgz",
+      "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
+      "dependencies": {
+        "uc.micro": "^2.0.0"
+      }
+    },
+    "node_modules/locate-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz",
+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "dev": true,
+      "dependencies": {
+        "p-locate": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+    },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+    },
+    "node_modules/lodash-unified": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
+      "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+      "peerDependencies": {
+        "@types/lodash-es": "*",
+        "lodash": "*",
+        "lodash-es": "*"
+      }
+    },
+    "node_modules/lodash.merge": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz",
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "dev": true
+    },
+    "node_modules/lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.8",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.8.tgz",
+      "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.4.15"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/markdown-it": {
+      "version": "14.1.0",
+      "resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-14.1.0.tgz",
+      "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+      "dependencies": {
+        "argparse": "^2.0.1",
+        "entities": "^4.4.0",
+        "linkify-it": "^5.0.0",
+        "mdurl": "^2.0.0",
+        "punycode.js": "^2.3.1",
+        "uc.micro": "^2.1.0"
+      },
+      "bin": {
+        "markdown-it": "bin/markdown-it.mjs"
+      }
+    },
+    "node_modules/markdown-it-image-figures": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/markdown-it-image-figures/-/markdown-it-image-figures-2.1.1.tgz",
+      "integrity": "sha512-mwXSQ2nPeVUzCMIE3HlLvjRioopiqyJLNph0pyx38yf9mpqFDhNGnMpAXF9/A2Xv0oiF2cVyg9xwfF0HNAz05g==",
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "markdown-it": "*"
+      }
+    },
+    "node_modules/markdown-it-task-lists": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz",
+      "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA=="
+    },
+    "node_modules/markdown-it-xss": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/markdown-it-xss/-/markdown-it-xss-1.0.2.tgz",
+      "integrity": "sha512-D52im1+e394EckqhK0AuyqxQ/WCuo09wJWInuCh6j1O2d6LqImJOu36lA6KZqImhWJeYIQnGeryaH3wXcro5MQ==",
+      "dependencies": {
+        "xss": "^1.0.6"
+      }
+    },
+    "node_modules/md-editor-v3": {
+      "version": "4.13.2",
+      "resolved": "https://registry.npmmirror.com/md-editor-v3/-/md-editor-v3-4.13.2.tgz",
+      "integrity": "sha512-4y3gNqLaH9TviGW25u1VnFfS/Q4vZpE0uyYIkWAIrT8xznNOO/fhKMJ24X9tqbzEhjjIPijKx/QOgB68+aAvBw==",
+      "dependencies": {
+        "@codemirror/lang-markdown": "^6.2.4",
+        "@codemirror/language-data": "^6.4.1",
+        "@types/markdown-it": "^13.0.7",
+        "@vavt/util": "^1.5.1",
+        "codemirror": "^6.0.1",
+        "copy-to-clipboard": "^3.3.3",
+        "lru-cache": "^10.2.0",
+        "markdown-it": "^14.0.0",
+        "markdown-it-image-figures": "^2.1.1",
+        "markdown-it-task-lists": "^2.1.1",
+        "markdown-it-xss": "^1.0.2",
+        "medium-zoom": "^1.1.0",
+        "punycode": "^2.3.1"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.47"
+      }
+    },
+    "node_modules/md-editor-v3/node_modules/lru-cache": {
+      "version": "10.2.0",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.2.0.tgz",
+      "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
+      "engines": {
+        "node": "14 || >=16.14"
+      }
+    },
+    "node_modules/mdurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-2.0.0.tgz",
+      "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="
+    },
+    "node_modules/medium-zoom": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/medium-zoom/-/medium-zoom-1.1.0.tgz",
+      "integrity": "sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ=="
+    },
+    "node_modules/memoize-one": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
+      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
+    },
+    "node_modules/memorystream": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmmirror.com/memorystream/-/memorystream-0.3.1.tgz",
+      "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
+    "node_modules/merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz",
+      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+      "dev": true,
+      "dependencies": {
+        "braces": "^3.0.2",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "9.0.3",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.3.tgz",
+      "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "node_modules/muggle-string": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.3.1.tgz",
+      "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==",
+      "dev": true
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.7",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
+      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+      "dev": true
+    },
+    "node_modules/node-releases": {
+      "version": "2.0.14",
+      "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.14.tgz",
+      "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
+      "dev": true
+    },
+    "node_modules/normalize-package-data": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-6.0.0.tgz",
+      "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==",
+      "dev": true,
+      "dependencies": {
+        "hosted-git-info": "^7.0.0",
+        "is-core-module": "^2.8.1",
+        "semver": "^7.3.5",
+        "validate-npm-package-license": "^3.0.4"
+      },
+      "engines": {
+        "node": "^16.14.0 || >=18.0.0"
+      }
+    },
+    "node_modules/normalize-package-data/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/normalize-package-data/node_modules/semver": {
+      "version": "7.5.4",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz",
+      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/normalize-package-data/node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/normalize-wheel-es": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw=="
+    },
+    "node_modules/npm-run-all2": {
+      "version": "6.1.1",
+      "resolved": "https://registry.npmmirror.com/npm-run-all2/-/npm-run-all2-6.1.1.tgz",
+      "integrity": "sha512-lWLbkPZ5BSdXtN8lR+0rc8caKoPdymycpZksyDEC9MOBvfdwTXZ0uVhb7bMcGeXv2/BKtfQuo6Zn3zfc8rxNXA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^6.2.1",
+        "cross-spawn": "^7.0.3",
+        "memorystream": "^0.3.1",
+        "minimatch": "^9.0.0",
+        "pidtree": "^0.6.0",
+        "read-pkg": "^8.0.0",
+        "shell-quote": "^1.7.3"
+      },
+      "bin": {
+        "npm-run-all": "bin/npm-run-all/index.js",
+        "npm-run-all2": "bin/npm-run-all/index.js",
+        "run-p": "bin/run-p/index.js",
+        "run-s": "bin/run-s/index.js"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0",
+        "npm": ">= 8"
+      }
+    },
+    "node_modules/npm-run-all2/node_modules/ansi-styles": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz",
+      "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/nth-check": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz",
+      "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+      "dev": true,
+      "dependencies": {
+        "boolbase": "^1.0.0"
+      }
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "dev": true,
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/optionator": {
+      "version": "0.9.3",
+      "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.3.tgz",
+      "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
+      "dev": true,
+      "dependencies": {
+        "@aashutoshrathi/word-wrap": "^1.2.3",
+        "deep-is": "^0.1.3",
+        "fast-levenshtein": "^2.0.6",
+        "levn": "^0.4.1",
+        "prelude-ls": "^1.2.1",
+        "type-check": "^0.4.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/p-limit": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dev": true,
+      "dependencies": {
+        "yocto-queue": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/p-locate": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz",
+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "dev": true,
+      "dependencies": {
+        "p-limit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "dependencies": {
+        "callsites": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/parse-json": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-7.1.1.tgz",
+      "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.21.4",
+        "error-ex": "^1.3.2",
+        "json-parse-even-better-errors": "^3.0.0",
+        "lines-and-columns": "^2.0.3",
+        "type-fest": "^3.8.0"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "node_modules/parse-json/node_modules/type-fest": {
+      "version": "3.13.1",
+      "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-3.13.1.tgz",
+      "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
+      "dev": true,
+      "engines": {
+        "node": ">=14.16"
+      }
+    },
+    "node_modules/path-browserify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
+      "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+      "dev": true
+    },
+    "node_modules/path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-type": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz",
+      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/picocolors": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz",
+      "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/pidtree": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmmirror.com/pidtree/-/pidtree-0.6.0.tgz",
+      "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
+      "dev": true,
+      "bin": {
+        "pidtree": "bin/pidtree.js"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/pinia": {
+      "version": "2.1.7",
+      "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.7.tgz",
+      "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
+      "dependencies": {
+        "@vue/devtools-api": "^6.5.0",
+        "vue-demi": ">=0.14.5"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.4.0",
+        "typescript": ">=4.4.4",
+        "vue": "^2.6.14 || ^3.3.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        },
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pinia/node_modules/vue-demi": {
+      "version": "0.14.6",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz",
+      "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.4.35",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.35.tgz",
+      "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
+      "dependencies": {
+        "nanoid": "^3.3.7",
+        "picocolors": "^1.0.0",
+        "source-map-js": "^1.0.2"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/postcss-selector-parser": {
+      "version": "6.0.15",
+      "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz",
+      "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==",
+      "dev": true,
+      "dependencies": {
+        "cssesc": "^3.0.0",
+        "util-deprecate": "^1.0.2"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/prelude-ls": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
+      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/prettier": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.1.1.tgz",
+      "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==",
+      "dev": true,
+      "bin": {
+        "prettier": "bin/prettier.cjs"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/prettier-linter-helpers": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+      "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+      "dev": true,
+      "dependencies": {
+        "fast-diff": "^1.1.2"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    },
+    "node_modules/punycode": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/punycode.js": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz",
+      "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "dev": true
+    },
+    "node_modules/read-pkg": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmmirror.com/read-pkg/-/read-pkg-8.1.0.tgz",
+      "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/normalize-package-data": "^2.4.1",
+        "normalize-package-data": "^6.0.0",
+        "parse-json": "^7.0.0",
+        "type-fest": "^4.2.0"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/reusify": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz",
+      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+      "dev": true,
+      "engines": {
+        "iojs": ">=1.0.0",
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rimraf": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz",
+      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+      "dev": true,
+      "dependencies": {
+        "glob": "^7.1.3"
+      },
+      "bin": {
+        "rimraf": "bin.js"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.9.3.tgz",
+      "integrity": "sha512-JnchF0ZGFiqGpAPjg3e89j656Ne4tTtCY1VZc1AxtoQcRIxjTu9jyYHBAtkDXE+X681n4un/nX9SU52AroSRzg==",
+      "dev": true,
+      "dependencies": {
+        "@types/estree": "1.0.5"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.9.3",
+        "@rollup/rollup-android-arm64": "4.9.3",
+        "@rollup/rollup-darwin-arm64": "4.9.3",
+        "@rollup/rollup-darwin-x64": "4.9.3",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.9.3",
+        "@rollup/rollup-linux-arm64-gnu": "4.9.3",
+        "@rollup/rollup-linux-arm64-musl": "4.9.3",
+        "@rollup/rollup-linux-riscv64-gnu": "4.9.3",
+        "@rollup/rollup-linux-x64-gnu": "4.9.3",
+        "@rollup/rollup-linux-x64-musl": "4.9.3",
+        "@rollup/rollup-win32-arm64-msvc": "4.9.3",
+        "@rollup/rollup-win32-ia32-msvc": "4.9.3",
+        "@rollup/rollup-win32-x64-msvc": "4.9.3",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "dev": true,
+      "dependencies": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
+    "node_modules/sass": {
+      "version": "1.69.7",
+      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.69.7.tgz",
+      "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": ">=3.0.0 <4.0.0",
+        "immutable": "^4.0.0",
+        "source-map-js": ">=0.6.2 <2.0.0"
+      },
+      "bin": {
+        "sass": "sass.js"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/semver": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "node_modules/shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
+      "dependencies": {
+        "shebang-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shell-quote": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.1.tgz",
+      "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
+      "dev": true
+    },
+    "node_modules/slash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz",
+      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz",
+      "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/spdx-correct": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.2.0.tgz",
+      "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
+      "dev": true,
+      "dependencies": {
+        "spdx-expression-parse": "^3.0.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "node_modules/spdx-exceptions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+      "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
+      "dev": true
+    },
+    "node_modules/spdx-expression-parse": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+      "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+      "dev": true,
+      "dependencies": {
+        "spdx-exceptions": "^2.1.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "node_modules/spdx-license-ids": {
+      "version": "3.0.16",
+      "resolved": "https://registry.npmmirror.com/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz",
+      "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==",
+      "dev": true
+    },
+    "node_modules/sse.js": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmmirror.com/sse.js/-/sse.js-2.4.1.tgz",
+      "integrity": "sha512-0qXPFZCClp+RPWtldNTYUjDWDlStZq1nyRF7at8WDFotHEyCivEBlR7Z5ftcJnt9KpouRg+NdzGFvPTB2gHl8Q=="
+    },
+    "node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/style-mod": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.2.tgz",
+      "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
+    },
+    "node_modules/supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/svg-tags": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz",
+      "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==",
+      "dev": true
+    },
+    "node_modules/synckit": {
+      "version": "0.8.8",
+      "resolved": "https://registry.npmmirror.com/synckit/-/synckit-0.8.8.tgz",
+      "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==",
+      "dev": true,
+      "dependencies": {
+        "@pkgr/core": "^0.1.0",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      }
+    },
+    "node_modules/text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+      "dev": true
+    },
+    "node_modules/to-fast-properties": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+      "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/toggle-selection": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz",
+      "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
+    },
+    "node_modules/ts-api-utils": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
+      "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==",
+      "dev": true,
+      "engines": {
+        "node": ">=16.13.0"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.2.0"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz",
+      "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
+      "dev": true
+    },
+    "node_modules/type-check": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz",
+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/type-fest": {
+      "version": "4.9.0",
+      "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-4.9.0.tgz",
+      "integrity": "sha512-KS/6lh/ynPGiHD/LnAobrEFq3Ad4pBzOlJ1wAnJx9N4EYoqFhMfLIBjUT2UEx4wg5ZE+cC1ob6DCSpppVo+rtg==",
+      "dev": true,
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.3.3",
+      "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.3.3.tgz",
+      "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
+      "devOptional": true,
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/uc.micro": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz",
+      "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="
+    },
+    "node_modules/undici-types": {
+      "version": "5.26.5",
+      "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz",
+      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+      "dev": true
+    },
+    "node_modules/universalify": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
+      "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 10.0.0"
+      }
+    },
+    "node_modules/update-browserslist-db": {
+      "version": "1.0.13",
+      "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
+      "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
+      "dev": true,
+      "dependencies": {
+        "escalade": "^3.1.1",
+        "picocolors": "^1.0.0"
+      },
+      "bin": {
+        "update-browserslist-db": "cli.js"
+      },
+      "peerDependencies": {
+        "browserslist": ">= 4.21.0"
+      }
+    },
+    "node_modules/uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "dependencies": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "dev": true
+    },
+    "node_modules/uuid": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz",
+      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+      "dev": true,
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
+    "node_modules/validate-npm-package-license": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+      "dev": true,
+      "dependencies": {
+        "spdx-correct": "^3.0.0",
+        "spdx-expression-parse": "^3.0.0"
+      }
+    },
+    "node_modules/vite": {
+      "version": "5.0.10",
+      "resolved": "https://registry.npmmirror.com/vite/-/vite-5.0.10.tgz",
+      "integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==",
+      "dev": true,
+      "dependencies": {
+        "esbuild": "^0.19.3",
+        "postcss": "^8.4.32",
+        "rollup": "^4.2.0"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^18.0.0 || >=20.0.0",
+        "less": "*",
+        "lightningcss": "^1.21.0",
+        "sass": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "terser": "^5.4.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue": {
+      "version": "3.4.21",
+      "resolved": "https://registry.npmmirror.com/vue/-/vue-3.4.21.tgz",
+      "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.4.21",
+        "@vue/compiler-sfc": "3.4.21",
+        "@vue/runtime-dom": "3.4.21",
+        "@vue/server-renderer": "3.4.21",
+        "@vue/shared": "3.4.21"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-eslint-parser": {
+      "version": "9.3.2",
+      "resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-9.3.2.tgz",
+      "integrity": "sha512-q7tWyCVaV9f8iQyIA5Mkj/S6AoJ9KBN8IeUSf3XEmBrOtxOZnfTg5s4KClbZBCK3GtnT/+RyCLZyDHuZwTuBjg==",
+      "dev": true,
+      "dependencies": {
+        "debug": "^4.3.4",
+        "eslint-scope": "^7.1.1",
+        "eslint-visitor-keys": "^3.3.0",
+        "espree": "^9.3.1",
+        "esquery": "^1.4.0",
+        "lodash": "^4.17.21",
+        "semver": "^7.3.6"
+      },
+      "engines": {
+        "node": "^14.17.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "eslint": ">=6.0.0"
+      }
+    },
+    "node_modules/vue-eslint-parser/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/vue-eslint-parser/node_modules/semver": {
+      "version": "7.5.4",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz",
+      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/vue-eslint-parser/node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    },
+    "node_modules/vue-router": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.2.5.tgz",
+      "integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==",
+      "dependencies": {
+        "@vue/devtools-api": "^6.5.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/vue-template-compiler": {
+      "version": "2.7.16",
+      "resolved": "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
+      "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==",
+      "dev": true,
+      "dependencies": {
+        "de-indent": "^1.0.2",
+        "he": "^1.2.0"
+      }
+    },
+    "node_modules/vue-tsc": {
+      "version": "1.8.27",
+      "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-1.8.27.tgz",
+      "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==",
+      "dev": true,
+      "dependencies": {
+        "@volar/typescript": "~1.11.1",
+        "@vue/language-core": "1.8.27",
+        "semver": "^7.5.4"
+      },
+      "bin": {
+        "vue-tsc": "bin/vue-tsc.js"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      }
+    },
+    "node_modules/vue-tsc/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/vue-tsc/node_modules/semver": {
+      "version": "7.5.4",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz",
+      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/vue-tsc/node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    },
+    "node_modules/w3c-keyname": {
+      "version": "2.2.8",
+      "resolved": "https://registry.npmmirror.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+      "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
+    },
+    "node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "dev": true
+    },
+    "node_modules/xml-name-validator": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+      "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/xss": {
+      "version": "1.0.15",
+      "resolved": "https://registry.npmmirror.com/xss/-/xss-1.0.15.tgz",
+      "integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==",
+      "dependencies": {
+        "commander": "^2.20.3",
+        "cssfilter": "0.0.10"
+      },
+      "bin": {
+        "xss": "bin/xss"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
+    "node_modules/yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "dev": true
+    },
+    "node_modules/yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      }
+    }
+  }
+}

+ 49 - 0
front-end/package.json

@@ -0,0 +1,49 @@
+{
+  "name": "uni-ai-admin",
+  "version": "0.0.0",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "run-p type-check \"build-only {@}\" --",
+    "preview": "vite preview",
+    "build-only": "vite build",
+    "type-check": "vue-tsc --build --force",
+    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
+    "format": "prettier --write src/",
+    "api": "node scripts/generate-api.js"
+  },
+  "dependencies": {
+    "@element-plus/icons-vue": "^2.3.1",
+    "axios": "^1.6.4",
+    "dayjs": "^1.11.10",
+    "element-plus": "^2.4.2",
+    "lodash": "^4.17.21",
+    "md-editor-v3": "^4.13.2",
+    "pinia": "^2.1.7",
+    "sse.js": "^2.4.1",
+    "vue": "^3.4.21",
+    "vue-router": "^4.2.5"
+  },
+  "devDependencies": {
+    "@rushstack/eslint-patch": "^1.3.3",
+    "@tsconfig/node18": "^18.2.2",
+    "@types/node": "^18.19.3",
+    "@vitejs/plugin-vue": "^4.5.2",
+    "@vitejs/plugin-vue-jsx": "^3.1.0",
+    "@vue/eslint-config-prettier": "^8.0.0",
+    "@vue/eslint-config-typescript": "^12.0.0",
+    "@vue/tsconfig": "^0.5.0",
+    "adm-zip": "^0.5.10",
+    "eslint": "^8.49.0",
+    "eslint-plugin-vue": "^9.17.0",
+    "fs-extra": "^11.1.1",
+    "npm-run-all2": "^6.1.1",
+    "prettier": "^3.0.3",
+    "sass": "^1.69.7",
+    "typescript": "~5.3.0",
+    "uuid": "^9.0.1",
+    "vite": "^5.0.10",
+    "vue-tsc": "^1.8.25"
+  }
+}

BIN
front-end/public/favicon.ico


+ 79 - 0
front-end/scripts/generate-api.js

@@ -0,0 +1,79 @@
+/* eslint-env node */
+import http from 'http'
+import fs from 'fs'
+import fse from 'fs-extra'
+import { v4 as uuidv4 } from 'uuid'
+import os from 'os'
+import path from 'path'
+import AdmZip from 'adm-zip'
+const sourceUrl = 'http://58.87.69.234:9902/ts.zip'
+//const sourceUrl = 'http://localhost:9902/ts.zip'
+const tmpFilePath = os.tmpdir() + '/' + uuidv4() + '.zip'
+const generatePath = 'src/apis/__generated'
+
+console.log('Downloading ' + sourceUrl + '...')
+
+const tmpFile = fs.createWriteStream(tmpFilePath)
+http.get(sourceUrl, (response) => {
+  response.pipe(tmpFile)
+  tmpFile.on('finish', () => {
+    tmpFile.close()
+    console.log('File save success: ', tmpFilePath)
+
+    // Remove generatePath if it exists
+    if (fs.existsSync(generatePath)) {
+      console.log('Removing existing generatePath...')
+      fse.removeSync(generatePath)
+      console.log('Existing generatePath removed.')
+    }
+
+    // Unzip the file using adm-zip
+    console.log('Unzipping the file...')
+    const zip = new AdmZip(tmpFilePath)
+    zip.extractAllTo(generatePath, true)
+    console.log('File unzipped successfully.')
+    // Remove the temporary file
+    console.log('Removing temporary file...')
+    fs.unlink(tmpFilePath, (err) => {
+      if (err) {
+        console.error('Error while removing temporary file:', err)
+      } else {
+        console.log('Temporary file removed.')
+      }
+    })
+    traverseDirectory(modelPath)
+    traverseDirectory(servicePath)
+  })
+})
+
+// 替换目录路径
+const modelPath = 'src/apis/__generated/model'
+const servicePath = 'src/apis/__generated/services'
+
+// 递归遍历目录中的所有文件
+function traverseDirectory(directoryPath) {
+  const files = fs.readdirSync(directoryPath)
+
+  files.forEach((file) => {
+    const filePath = path.join(directoryPath, file)
+    const stats = fs.statSync(filePath)
+
+    if (stats.isDirectory()) {
+      traverseDirectory(filePath)
+    } else if (stats.isFile() && path.extname(filePath) === '.ts') {
+      replaceInFile(filePath)
+    }
+  })
+}
+
+// 替换文件中的文本
+function replaceInFile(filePath) {
+  const fileContent = fs.readFileSync(filePath, 'utf8')
+  const updatedContent = fileContent
+    .replaceAll('readonly ', '')
+    .replace(/ReadonlyArray/g, 'Array')
+    .replaceAll('ReadonlyMap', 'Map')
+    .replace(/Map<(\S+), (\S+)>/g, '{ [key: $1]: $2 }')
+  // .replace(/query: (\S+)/g, 'query: T')
+  fs.writeFileSync(filePath, updatedContent, 'utf8')
+}

+ 78 - 0
front-end/src/App.vue

@@ -0,0 +1,78 @@
+<script setup lang="ts">
+import { RouterView } from 'vue-router'
+import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
+</script>
+
+<template>
+  <el-config-provider :locale="zhCn">
+    <router-view v-slot="{ Component }">
+      <transition name="slide">
+        <component :is="Component" />
+      </transition> </router-view
+  ></el-config-provider>
+</template>
+
+<style>
+body {
+  margin: 0;
+  font-family:
+    Noto Sans SC,
+    SF Pro SC,
+    SF Pro Text,
+    SF Pro Icons,
+    PingFang SC,
+    Helvetica Neue,
+    Helvetica,
+    Arial,
+    sans-serif;
+}
+
+.el-date-editor--datetime {
+  width: 160px !important;
+}
+
+::-webkit-scrollbar {
+  --bar-width: 5px;
+  width: 5px;
+  height: 5px;
+}
+
+::-webkit-scrollbar-track {
+  background-color: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+  background-color: rgba(0, 0, 0, 0.1);
+  border-radius: 20px;
+  background-clip: content-box;
+  border: 1px solid transparent;
+}
+
+.slide-enter-active {
+  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
+}
+
+.slide-leave-active {
+  transition: all 0.3s ease-out;
+}
+
+.slide-enter-from {
+  transform: translateX(-100vw);
+  opacity: 0;
+}
+
+.slide-enter-to {
+  transform: translateX(0);
+  opacity: 1;
+}
+
+.slide-leave-from {
+  transform: translateX(0);
+  opacity: 1;
+}
+
+.slide-leave-to {
+  transform: translateX(100vw);
+  opacity: 0;
+}
+</style>

BIN
front-end/src/assets/SrpingAI知识点.png


BIN
front-end/src/assets/background.jpg


BIN
front-end/src/assets/cover.png


BIN
front-end/src/assets/logo.jpg


+ 83 - 0
front-end/src/components/image/image-upload.vue

@@ -0,0 +1,83 @@
+<script lang="tsx">
+import { defineComponent, ref } from 'vue'
+import { ElIcon, ElMessage, ElUpload, type UploadProps } from 'element-plus'
+import { Plus } from '@element-plus/icons-vue'
+import type { Result } from '@/typings'
+const API_PREFIX = import.meta.env.VITE_API_PREFIX
+
+export default defineComponent({
+  props: {
+    modelValue: { type: String, default: '' },
+    size: { type: Number, default: 120 }
+  },
+  emits: ['update:modelValue'],
+  setup(props, { emit }) {
+    const loading = ref(false)
+    const handleImageSuccess: UploadProps['onSuccess'] = (response: Result<string>) => {
+      loading.value = false
+      emit('update:modelValue', response.result)
+    }
+    const types = ['image/png', 'image/jpeg', 'image/webp']
+
+    const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile) => {
+      if (!types.includes(rawFile.type)) {
+        ElMessage.error('Image picture must be JPG format!')
+        return false
+      } else if (rawFile.size / 1024 / 1024 > 2) {
+        ElMessage.error('Image picture size can not exceed 2MB!')
+        return false
+      }
+      loading.value = true
+      return true
+    }
+    return () => (
+      <ElUpload
+        beforeUpload={beforeImageUpload}
+        onSuccess={handleImageSuccess}
+        action={API_PREFIX + '/oss/upload'}
+        showFileList={false}
+        class="image-uploader"
+        v-loading={loading.value}
+      >
+        {props.modelValue ? (
+          <img
+            alt="图片"
+            src={props.modelValue}
+            style={{
+              objectFit: 'cover',
+              width: `${props.size}px`,
+              height: `${props.size}px`,
+              display: 'block'
+            }}
+          />
+        ) : (
+          <ElIcon size={props.size} class="image-uploader-icon">
+            <Plus />
+          </ElIcon>
+        )}
+      </ElUpload>
+    )
+  }
+})
+</script>
+
+<style scoped>
+.image-uploader .el-upload {
+  border: 1px dashed var(--el-border-color);
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+  transition: var(--el-transition-duration-fast);
+}
+
+.image-uploader .el-upload:hover {
+  border-color: var(--el-color-primary);
+}
+
+.el-icon.image-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  text-align: center;
+}
+</style>

+ 92 - 0
front-end/src/components/key-value/key-value-input.vue

@@ -0,0 +1,92 @@
+<template>
+  <div class="key-value-section">
+    <div class="key-value-wrapper" v-for="(keyValue, index) in keyValueList" :key="index">
+      <el-button
+        class="close-btn"
+        type="warning"
+        size="small"
+        circle
+        @click="deleteKeyValue(index)"
+      >
+        <el-icon>
+          <close></close>
+        </el-icon>
+      </el-button>
+      <el-form label-width="80" label-position="left">
+        <el-form-item label="键名称">
+          <el-input style="width: 100px" v-model="keyValueList[index].name" size="small">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="值列表">
+          <value-input v-model="keyValueList[index].values"></value-input>
+        </el-form-item>
+      </el-form>
+    </div>
+    <el-button type="primary" size="small" class="plus" @click="addKeyValue">
+      <el-icon>
+        <plus></plus>
+      </el-icon>
+    </el-button>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+import { Close, Plus } from '@element-plus/icons-vue'
+import ValueInput from './value-input.vue'
+import type { KeyValue } from '@/apis/__generated/model/static'
+
+const props = defineProps<{ modelValue?: KeyValue[] }>()
+const emit = defineEmits<{
+  change: [data: KeyValue[]]
+  'update:modelValue': [data: KeyValue[]]
+}>()
+const keyValueList = ref<KeyValue[]>([])
+const deleteKeyValue = (index: number) => {
+  keyValueList.value.splice(index, 1)
+}
+const addKeyValue = () => {
+  keyValueList.value.push({
+    name: '',
+    values: []
+  })
+}
+watch(
+  () => props.modelValue,
+  (newValue) => {
+    if (newValue) {
+      keyValueList.value = newValue
+    }
+  }
+)
+watch(
+  () => keyValueList.value,
+  () => {
+    emit('update:modelValue', keyValueList.value)
+  },
+  { deep: true }
+)
+</script>
+
+<style lang="scss" scoped>
+.key-value-section {
+  width: 100%;
+  .plus {
+    margin-top: 20px;
+  }
+  .key-value-wrapper {
+    margin-bottom: 20px;
+    border: rgba(114, 207, 222, 0.5) 1px dashed;
+    padding: 10px;
+    position: relative;
+    margin-top: 20px;
+    .close-btn {
+      position: absolute;
+      top: 5px;
+      right: 5px;
+      width: 15px;
+      height: 15px;
+    }
+  }
+}
+</style>

+ 135 - 0
front-end/src/components/key-value/value-input.vue

@@ -0,0 +1,135 @@
+<template>
+  <div class="values-chose">
+    <div class="values">
+      <!-- 显示当内的多个值,用','连接多个值 -->
+      <el-input
+        class="values-show"
+        size="small"
+        readonly
+        :model-value="props.modelValue.join(',')"
+      ></el-input>
+    </div>
+    <div class="values-input">
+      <template v-for="(tag, index) in tags" :key="tag">
+        <!-- 编辑值 -->
+        <el-input
+          v-if="editIndex === index"
+          v-model="editInputValue"
+          class="value value-edit"
+          size="small"
+          @blur="handleEditConfirm"
+          @keydown.enter="handleEditConfirm"
+        ></el-input>
+        <!-- 显示值 -->
+        <el-tag
+          v-else
+          class="value"
+          closable
+          :disable-transitions="false"
+          @close="handleClose(index)"
+          @click="handleEdit(index)"
+          >{{ tag }}</el-tag
+        >
+      </template>
+      <!-- 点击新增显示输入框,否则显示新增按钮  -->
+      <el-input
+        v-if="inputVisible"
+        ref="inputRef"
+        v-model="inputValue"
+        class="value-input"
+        size="small"
+        @blur="handleInputConfirm"
+        @keydown.enter="handleInputConfirm"
+      ></el-input>
+      <el-button v-else size="small" @click="showInput"> + 新增值 </el-button>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+import { computed, nextTick, ref } from 'vue'
+import { ElButton, ElInput, ElTag } from 'element-plus'
+
+const props = defineProps({ modelValue: { type: Array<string>, required: false, default: '' } })
+const emit = defineEmits<{ 'update:modelValue': [values: string[]] }>()
+
+const tags = computed({
+  get: () => {
+    return props.modelValue ? props.modelValue : []
+  },
+  set: (values) => {
+    emit('update:modelValue', values)
+  }
+})
+// 编辑值
+const editIndex = ref(-1)
+const editInputValue = ref('')
+// 点击值标签,显示输入框编辑值
+const handleEdit = (index: number) => {
+  editInputValue.value = tags.value[index]
+  editIndex.value = index
+}
+// 编辑值输入框确认
+const handleEditConfirm = () => {
+  if (editInputValue.value === tags.value[editIndex.value]) {
+    return
+  }
+  tags.value[editIndex.value] = editInputValue.value
+  tags.value = [...tags.value]
+  editIndex.value = -1
+}
+// 点击值标签的右上角触发删除值
+const handleClose = (index: number) => {
+  tags.value.splice(index, 1)
+  tags.value = [...tags.value]
+}
+
+// 新增值
+const inputRef = ref<InstanceType<typeof ElInput>>()
+const inputVisible = ref(false)
+const inputValue = ref('')
+const handleInputConfirm = () => {
+  if (inputValue.value) {
+    tags.value = [...tags.value, inputValue.value]
+  }
+  inputVisible.value = false
+  inputValue.value = ''
+}
+const showInput = () => {
+  inputVisible.value = true
+  nextTick(() => {
+    if (inputRef.value && inputRef.value.input) {
+      inputRef.value.input.focus()
+    }
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.values {
+  display: flex;
+  justify-content: flex-start;
+
+  .values-show {
+    width: 100px;
+  }
+}
+
+.values-input {
+  margin-top: 10px;
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  margin-bottom: 10px;
+  .value-edit {
+    width: 100px;
+  }
+
+  .value {
+    margin-right: 5px;
+  }
+
+  .value-input {
+    width: 100px;
+  }
+}
+</style>

+ 19 - 0
front-end/src/main.ts

@@ -0,0 +1,19 @@
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+import App from './App.vue'
+import router from './router'
+import { MdPreview } from 'md-editor-v3'
+import 'md-editor-v3/lib/preview.css'
+const app = createApp(App)
+
+app.use(createPinia())
+app.use(ElementPlus)
+app.use(router)
+app.component('MdPreview', MdPreview)
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component)
+}
+app.mount('#app')

+ 34 - 0
front-end/src/router/index.ts

@@ -0,0 +1,34 @@
+import { createRouter, createWebHashHistory } from 'vue-router'
+import RegisterView from '@/views/login/register-view.vue'
+import LoginView from '@/views/login/login-view.vue'
+import ChatView from '@/views/chat/chat-view.vue'
+import AnalyzeResultView from '@/views/code/analyze/analyze-result-view.vue'
+
+const router = createRouter({
+  history: createWebHashHistory(import.meta.env.BASE_URL),
+  routes: [
+    {
+      path: '/',
+      component: ChatView
+    },
+    {
+      path: '/login',
+      name: 'login',
+      component: LoginView
+    },
+    {
+      path: '/register',
+      name: 'register',
+      component: RegisterView
+    },
+    {
+      path: '/analyze',
+      component: AnalyzeResultView,
+      props(to) {
+        return { path: to.query.path }
+      }
+    }
+  ]
+})
+
+export default router

+ 19 - 0
front-end/src/stores/home-store.ts

@@ -0,0 +1,19 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import type { UserDto } from '@/apis/__generated/model/dto'
+import { api } from '@/utils/api-instance'
+
+export const useHomeStore = defineStore('home', () => {
+  const userInfo = ref<UserDto['UserRepository/FETCHER']>()
+  const getUserInfo = async () => {
+    userInfo.value = await api.userController.userInfo()
+    return userInfo.value
+  }
+  const init = async () => {
+    await getUserInfo()
+  }
+  const logout = () => {
+    userInfo.value = undefined
+  }
+  return { userInfo, getUserInfo, init, logout }
+})

+ 17 - 0
front-end/src/typings/index.d.ts

@@ -0,0 +1,17 @@
+import type { MenuDto } from '@/apis/__generated/model/dto'
+
+export type EditMode = 'CREATE' | 'UPDATE'
+export interface Scope<T> {
+  row: T
+  $index: number
+}
+
+export interface Result<T> {
+  code: number
+  success: boolean
+  msg: string
+  result: T
+}
+export type MenuTreeDto = {
+  children?: MenuTreeDto[]
+} & MenuDto['MenuRepository/SIMPLE_FETCHER']

+ 6 - 0
front-end/src/utils/api-instance.ts

@@ -0,0 +1,6 @@
+import { Api } from '@/apis/__generated'
+import { request } from '@/utils/request'
+// 导出全局变量`apis`
+export const api = new Api(async ({ uri, method, body }) => {
+  return await request({ url: uri, method, data: body })
+})

+ 22 - 0
front-end/src/utils/common.ts

@@ -0,0 +1,22 @@
+import { ElMessage } from 'element-plus'
+import type { ValidateFieldsError } from 'async-validator/dist-types'
+
+export const assertFormValidate = (callback: () => void) => {
+  return (valid: boolean, fields: ValidateFieldsError | undefined) => {
+    if (valid) {
+      callback()
+    } else {
+      if (fields) {
+        const messages: string[] = []
+        for (const field in fields) {
+          fields[field].forEach((error) => {
+            if (error.message) {
+              messages.push(error.message)
+            }
+          })
+        }
+        ElMessage.error({ message: messages.join('\n') })
+      }
+    }
+  }
+}

+ 23 - 0
front-end/src/utils/request.ts

@@ -0,0 +1,23 @@
+import axios from 'axios'
+import { ElMessage } from 'element-plus'
+import router from '@/router'
+
+const BASE_URL = import.meta.env.VITE_API_PREFIX
+export const request = axios.create({
+  baseURL: BASE_URL,
+  timeout: 600000
+})
+request.interceptors.response.use(
+  (res) => {
+    return res.data.result
+  },
+  ({ response }) => {
+    if (response.data.code !== 1) {
+      ElMessage.warning({ message: response.data.msg })
+    }
+    if (response.data.code === 10012) {
+      router.push('/login')
+    }
+    return Promise.reject(response.data.result)
+  }
+)

+ 383 - 0
front-end/src/views/chat/chat-view.vue

@@ -0,0 +1,383 @@
+<script lang="ts" setup>
+import { nextTick, onMounted, ref } from 'vue'
+import SessionItem from './components/session-item.vue'
+import { ChatRound, Close, Delete, EditPen } from '@element-plus/icons-vue'
+import MessageRow from './components/message-row.vue'
+import MessageInput from './components/message-input.vue'
+import { storeToRefs } from 'pinia'
+import { ElIcon, ElMessage, type UploadProps, type UploadUserFile } from 'element-plus'
+import { api } from '@/utils/api-instance'
+import { SSE } from 'sse.js'
+import { type AiMessage, useChatStore } from './store/chat-store'
+import type { AiMessageParams, AiMessageWrapper } from '@/apis/__generated/model/static'
+
+type ChatResponse = {
+  metadata: {
+    usage: {
+      totalTokens: number
+    }
+  }
+  result: {
+    metadata: {
+      finishReason: string
+    }
+    output: {
+      messageType: string
+      content: string
+    }
+  }
+}
+const API_PREFIX = import.meta.env.VITE_API_PREFIX
+const chatStore = useChatStore()
+const { handleDeleteSession, handleUpdateSession, handleClearMessage } = chatStore
+const { activeSession, sessionList, isEdit } = storeToRefs(chatStore)
+const messageListRef = ref<InstanceType<typeof HTMLDivElement>>()
+const loading = ref(true)
+
+onMounted(async () => {
+  // 查询自己的聊天会话
+  api.aiSessionController.findByUser().then((res) => {
+    // 讲会话添加到列表中
+    sessionList.value = res.map((row) => {
+      return { ...row, checked: false }
+    })
+    // 默认选中的聊天会话是第一个
+    if (sessionList.value.length > 0) {
+      activeSession.value = sessionList.value[0]
+    } else {
+      handleSessionCreate()
+    }
+    loading.value = false
+  })
+})
+
+// ChatGPT的回复
+const responseMessage = ref<AiMessage>({
+  id: new Date().getTime().toString(),
+  type: 'ASSISTANT',
+  medias: [],
+  textContent: '',
+  sessionId: ''
+})
+
+const handleSendMessage = async (message: { text: string; image: string }) => {
+  if (!activeSession.value) {
+    ElMessage.warning('请创建会话')
+    return
+  }
+  // 图片/语音
+  const medias: AiMessage['medias'] = []
+  if (message.image) {
+    medias.push({ type: 'image', data: message.image })
+  }
+  // 用户的提问
+  const chatMessage = {
+    id: new Date().getTime().toString(),
+    sessionId: activeSession.value.id,
+    medias,
+    textContent: message.text,
+    type: 'USER'
+  } satisfies AiMessage
+
+  responseMessage.value = {
+    id: new Date().getTime().toString(),
+    medias: [],
+    type: 'ASSISTANT',
+    textContent: '',
+    sessionId: activeSession.value.id
+  }
+  const body: AiMessageWrapper = { message: chatMessage, params: options.value }
+  const form = new FormData()
+  form.set('input', JSON.stringify(body))
+
+  if (fileList.value.length && fileList.value[0].raw) {
+    form.append('file', fileList.value[0].raw)
+  }
+  const evtSource = new SSE(API_PREFIX + '/message/chat', {
+    withCredentials: true,
+    // 禁用自动启动,需要调用stream()方法才能发起请求
+    start: false,
+    payload: form as any,
+    method: 'POST'
+  })
+  evtSource.addEventListener('message', async (event: any) => {
+    const response = JSON.parse(event.data) as ChatResponse
+    const finishReason = response.result.metadata.finishReason
+    if (response.result.output.content) {
+      responseMessage.value.textContent += response.result.output.content
+      // 滚动到底部
+      await nextTick(() => {
+        messageListRef.value?.scrollTo(0, messageListRef.value.scrollHeight)
+      })
+    }
+    if (finishReason && finishReason.toLowerCase() == 'stop') {
+      evtSource.close()
+      // 保存用户的提问
+      await api.aiMessageController.save({ body: chatMessage })
+      // 保存大模型的回复
+      await api.aiMessageController.save({ body: responseMessage.value })
+    }
+  })
+
+  // 调用stream,发起请求。
+  evtSource.stream()
+  // 将两条消息显示在页面中
+  activeSession.value.messages.push(...[chatMessage, responseMessage.value])
+  await nextTick(() => {
+    messageListRef.value?.scrollTo(0, messageListRef.value.scrollHeight)
+  })
+}
+
+const handleSessionCreate = () => {
+  chatStore.handleCreateSession({ name: '新的聊天' })
+}
+const options = ref<AiMessageParams>({
+  enableVectorStore: false,
+  enableAgent: false
+})
+const embeddingLoading = ref(false)
+const onUploadSuccess = () => {
+  embeddingLoading.value = false
+  ElMessage.success('上传成功')
+}
+const beforeUpload: UploadProps['beforeUpload'] = () => {
+  embeddingLoading.value = true
+  return true
+}
+const fileList = ref<UploadUserFile[]>([])
+</script>
+<template>
+  <!-- 最外层页面于窗口同宽,使聊天面板居中 -->
+  <div class="home-view">
+    <!-- 整个聊天面板 -->
+    <div class="chat-panel" v-loading="loading">
+      <!-- 左侧的会话列表 -->
+      <div class="session-panel">
+        <div class="title">AI助手</div>
+        <div class="session-list" v-if="activeSession">
+          <!-- for循环遍历会话列表用会话组件显示,并监听点击事件和删除事件。点击时切换到被点击的会话,删除时从会话列表中提出被删除的会话。 -->
+          <session-item
+            v-for="session in sessionList"
+            :key="session.id"
+            :active="session.id === activeSession.id"
+            :session="session"
+            class="session"
+            @click="activeSession = session"
+            @delete="handleDeleteSession"
+          ></session-item>
+        </div>
+        <div class="button-wrapper">
+          <el-button
+            style="margin-right: 20px"
+            :icon="ChatRound"
+            size="small"
+            @click="handleSessionCreate"
+            >创建会话
+          </el-button>
+        </div>
+      </div>
+      <!-- 右侧的消息记录 -->
+      <div class="message-panel">
+        <!-- 会话名称 -->
+        <div class="header" v-if="activeSession">
+          <div class="front">
+            <!-- 如果处于编辑状态则显示输入框让用户去修改 -->
+            <div v-if="isEdit" class="title">
+              <!-- 按回车代表确认修改 -->
+              <el-input
+                v-model="activeSession.name"
+                @keydown.enter="handleUpdateSession"
+              ></el-input>
+            </div>
+            <!-- 否则正常显示标题 -->
+            <div v-else class="title">{{ activeSession.name }}</div>
+            <div class="description">{{ activeSession.messages.length }}条对话</div>
+          </div>
+          <!-- 尾部的编辑按钮 -->
+          <div class="rear">
+            <el-icon :size="20" style="margin-right: 10px">
+              <Delete @click="handleClearMessage(activeSession.id)" />
+            </el-icon>
+            <el-icon :size="20">
+              <!-- 不处于编辑状态显示编辑按钮 -->
+              <EditPen v-if="!isEdit" @click="isEdit = true" />
+              <!-- 处于编辑状态显示取消编辑按钮 -->
+              <Close v-else @click="isEdit = false"></Close>
+            </el-icon>
+          </div>
+        </div>
+        <el-divider :border-style="'solid'" />
+        <div ref="messageListRef" class="message-list">
+          <!-- 过渡效果 -->
+          <transition-group name="list" v-if="activeSession">
+            <message-row
+              v-for="message in activeSession.messages"
+              :key="message.id"
+              :message="message"
+            ></message-row>
+          </transition-group>
+        </div>
+        <!-- 监听发送事件 -->
+        <message-input @send="handleSendMessage" v-if="activeSession"></message-input>
+      </div>
+      <div class="option-panel">
+        <el-form size="small">
+          <el-form-item>
+            <el-upload
+              v-loading="embeddingLoading"
+              :action="`${API_PREFIX}/document/embedding`"
+              :show-file-list="false"
+              :on-success="onUploadSuccess"
+              :before-upload="beforeUpload"
+            >
+              <el-button type="primary">上传文档</el-button>
+            </el-upload>
+          </el-form-item>
+          <el-form-item label="知识库">
+            <el-switch v-model="options.enableVectorStore"></el-switch>
+          </el-form-item>
+          <el-form-item label="agent(智能体)">
+            <el-switch v-model="options.enableAgent"></el-switch>
+          </el-form-item>
+          <el-form-item label="文件">
+            <div class="upload">
+              <el-upload v-model:file-list="fileList" :auto-upload="false" :limit="1">
+                <el-button type="primary">上传文本文件</el-button>
+              </el-upload>
+            </div>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+  </div>
+</template>
+<style lang="scss" scoped>
+.home-view {
+  width: 100vw;
+  height: 100vh;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  .chat-panel {
+    display: flex;
+    background-color: white;
+    width: 90%;
+    height: 90%;
+    box-shadow: 0 0 10px rgba(black, 0.1);
+    border-radius: 10px;
+
+    .session-panel {
+      display: flex;
+      flex-direction: column;
+      box-sizing: border-box;
+      padding: 20px;
+      position: relative;
+      border-right: 1px solid rgba(black, 0.07);
+      background-color: rgb(231, 248, 255);
+      height: 100%;
+      /* 标题 */
+      .title {
+        margin-top: 20px;
+        font-size: 20px;
+      }
+
+      .session-list {
+        overflow-y: scroll;
+        margin: 20px 0;
+        flex: 1;
+
+        .session {
+          /* 每个会话之间留一些间距 */
+          margin-top: 20px;
+        }
+
+        .session:first-child {
+          margin-top: 0;
+        }
+      }
+
+      .button-wrapper {
+        /* entity-panel是相对布局,这边的button-wrapper是相对它绝对布局 */
+        bottom: 20px;
+        left: 0;
+        display: flex;
+        /* 让内部的按钮显示在右侧 */
+        justify-content: flex-end;
+        /* 宽度和session-panel一样宽*/
+        width: 100%;
+
+        /* 按钮于右侧边界留一些距离 */
+        .new-session {
+          margin-right: 20px;
+        }
+      }
+    }
+
+    /* 右侧消息记录面板*/
+    .message-panel {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+
+      .header {
+        padding: 20px 20px 0 20px;
+        display: flex;
+        /* 会话名称和编辑按钮在水平方向上分布左右两边 */
+        justify-content: space-between;
+
+        /* 前部的标题和消息条数 */
+        .front {
+          .title {
+            color: rgba(black, 0.7);
+            font-size: 20px;
+          }
+
+          .description {
+            margin-top: 10px;
+            color: rgba(black, 0.5);
+          }
+        }
+
+        /* 尾部的编辑和取消编辑按钮 */
+        .rear {
+          display: flex;
+          align-items: center;
+        }
+      }
+
+      .message-list {
+        padding: 15px;
+        width: 100%;
+        flex: 1;
+        box-sizing: border-box;
+        // 消息条数太多时,溢出部分滚动
+        overflow-y: scroll;
+        // 当切换聊天会话时,消息记录也随之切换的过渡效果
+        .list-enter-active,
+        .list-leave-active {
+          transition: all 0.5s ease;
+        }
+
+        .list-enter-from,
+        .list-leave-to {
+          opacity: 0;
+          transform: translateX(30px);
+        }
+      }
+    }
+
+    //  选项面板
+    .option-panel {
+      width: 200px;
+      padding: 20px;
+      border-left: 1px solid rgba(black, 0.07);
+
+      .upload {
+        width: 160px;
+      }
+    }
+  }
+}
+</style>

+ 21 - 0
front-end/src/views/chat/components/markdown-message.vue

@@ -0,0 +1,21 @@
+<script setup lang="ts">
+import { MdPreview } from 'md-editor-v3'
+
+defineProps<{ message: string }>()
+</script>
+
+<template>
+  <!--  markdown显示消息-->
+  <md-preview id="preview-only" :model-value="message" preview-theme="smart-blue"></md-preview>
+</template>
+
+<style scoped lang="scss">
+// 调整markdown组件的一些样式,deep可以修改组件内的样式,正常情况是scoped只能修改本组件的样式。
+:deep(.md-editor-preview-wrapper) {
+  padding: 0 10px;
+
+  .smart-blue-theme p {
+    line-height: unset;
+  }
+}
+</style>

+ 76 - 0
front-end/src/views/chat/components/message-input.vue

@@ -0,0 +1,76 @@
+<script lang="ts" setup>
+import { ref } from 'vue'
+import { Position } from '@element-plus/icons-vue'
+import ImageUpload from '@/components/image/image-upload.vue'
+import { ElMessage } from 'element-plus'
+type Message = {
+  text: string
+  image: string
+}
+// 发送消息消息事件
+const emit = defineEmits<{
+  send: [message: Message]
+}>()
+// 输入框内的消息
+const message = ref<Message>({ text: '', image: '' })
+const sendMessage = () => {
+  if (!message.value.text) {
+    ElMessage.warning('请输入消息')
+    return
+  }
+  emit('send', message.value)
+  // 发送完清除
+  message.value = { text: '', image: '' }
+}
+</script>
+
+<template>
+  <div class="message-input">
+    <div class="input-wrapper">
+      <!-- 按回车键发送,输入框高度三行 -->
+      <el-input
+        v-model="message.text"
+        :autosize="false"
+        :rows="3"
+        class="input"
+        resize="none"
+        type="textarea"
+        @keydown.enter.prevent="sendMessage"
+      >
+      </el-input>
+      <div class="button-wrapper">
+        <image-upload class="image" :size="40" v-model="message.image"></image-upload>
+        <el-button type="primary" @click="sendMessage">
+          <el-icon class="el-icon--left">
+            <Position />
+          </el-icon>
+          发送
+        </el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.message-input {
+  padding: 20px 20px 0 20px;
+  border-top: 1px solid rgba(black, 0.07);
+  border-left: 1px solid rgba(black, 0.07);
+  border-right: 1px solid rgba(black, 0.07);
+  border-top-right-radius: 5px;
+  border-top-left-radius: 5px;
+  .el-form-item {
+    align-items: center;
+  }
+}
+
+.button-wrapper {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  padding: 20px;
+  .image {
+    margin-right: 20px;
+  }
+}
+</style>

+ 112 - 0
front-end/src/views/chat/components/message-row.vue

@@ -0,0 +1,112 @@
+<script lang="tsx" setup>
+import TextLoading from './text-loading.vue'
+import logo from '@/assets/logo.jpg'
+import MarkdownMessage from './markdown-message.vue'
+import type { AiMessage } from '../store/chat-store'
+import { computed } from 'vue'
+// message:接受消息对象,展示消息内容和头像,并且根据角色调整消息位置。
+// avatar:用户头像,如果角色是 Assistant则使用 logo。
+const props = defineProps<{
+  message: AiMessage
+  avatar?: string
+}>()
+
+const images = computed(() => {
+  const medias = props.message.medias || []
+  return medias.filter((media) => media.type === 'image').map((media) => media.data)
+})
+</script>
+
+<!-- 整个div是用来调整内部消息的位置,每条消息占的空间都是一整行,然后根据right还是left来调整内部的消息是靠右边还是靠左边 -->
+<template>
+  <div :class="['message-row', message.type === 'USER' ? 'right' : 'left']">
+    <!-- 消息展示,分为上下,上面是头像,下面是消息 -->
+    <div class="row">
+      <!-- 头像, -->
+      <div class="avatar-wrapper">
+        <el-avatar
+          :src="avatar || logo"
+          class="avatar"
+          shape="square"
+          v-if="message.type === 'USER'"
+        />
+        <el-avatar :src="logo" class="avatar" shape="square" v-else />
+      </div>
+      <!-- 发送的消息或者回复的消息 -->
+      <div class="message">
+        <!-- 如果消息是文本,用markdown展示 -->
+        <markdown-message
+          :type="message.type"
+          :message="message.textContent"
+          v-if="message.textContent"
+        ></markdown-message>
+        <!-- 如果消息的内容是图片,则显示图片  -->
+        <el-image
+          v-for="image in images"
+          :key="image"
+          class="image"
+          fit="cover"
+          :preview-src-list="images"
+          :src="image"
+        ></el-image>
+        <!-- 如果消息的内容为空则显示加载动画 -->
+        <TextLoading v-if="!message.textContent && !images.length"></TextLoading>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.message-row {
+  display: flex;
+
+  &.right {
+    // 消息显示在右侧
+    justify-content: flex-end;
+
+    .row {
+      // 头像也要靠右侧
+      .avatar-wrapper {
+        display: flex;
+        justify-content: flex-end;
+      }
+
+      // 用户回复的消息和ChatGPT回复的消息背景颜色做区分
+      .message {
+        background-color: rgb(231, 248, 255);
+        :deep(.md-editor) {
+          background-color: rgb(231, 248, 255);
+        }
+      }
+    }
+  }
+
+  // 默认靠左边显示
+  .row {
+    .avatar-wrapper {
+      .avatar {
+        box-shadow: 20px 20px 20px 3px rgba(0, 0, 0, 0.01);
+        margin-bottom: 20px;
+      }
+    }
+
+    .message {
+      font-size: 15px;
+      padding: 1.5px;
+      // 限制消息展示的最大宽度
+      max-width: 800px;
+      // 圆润一点
+      border-radius: 7px;
+      // 给消息框加一些描边,看起来更加实一些,要不然太扁了轻飘飘的。
+      border: 1px solid rgba(black, 0.1);
+      // 背景颜色
+      background-color: #f4f4f5;
+
+      .image {
+        width: 600px;
+        height: 600px;
+      }
+    }
+  }
+}
+</style>

+ 140 - 0
front-end/src/views/chat/components/session-item.vue

@@ -0,0 +1,140 @@
+<script lang="ts" setup>
+import { CircleClose } from '@element-plus/icons-vue'
+import type { AiSession } from '@/views/chat/store/chat-store'
+
+// entity:用于展示的会话信息
+const prop = defineProps<{ active: boolean; session: AiSession }>()
+// 定义删除事件,当触发删除事件时会向外部发送被删除的会话。
+const emit = defineEmits<{
+  delete: [session: AiSession]
+}>()
+// 当鼠标放到会话上时,会弹出删除图标,点击删除图标发送删除事件。
+const handleDeleteSession = () => {
+  emit('delete', prop.session)
+}
+</script>
+
+<template>
+  <!-- 如果处于激活状态则增加 active class -->
+  <div :class="['session-item', active ? 'active' : '']">
+    <!-- 会话的名称 -->
+    <div class="name">{{ session.name }}</div>
+    <!-- 会话内的消息数量和最近修改的时间 -->
+    <div class="count-time">
+      <div class="count">{{ session.messages ? session.messages.length : 0 }}条对话</div>
+      <div class="time">{{ session.editedTime }}</div>
+    </div>
+    <!-- 当鼠标放在会话上时会弹出遮罩 -->
+    <div class="mask"></div>
+    <!-- 当鼠标放在会话上时会弹出删除按钮 -->
+    <div class="btn-wrapper">
+      <el-icon :size="15" class="close">
+        <el-popconfirm title="是否确认永久删除该聊天会话?" @confirm="handleDeleteSession">
+          <template #reference>
+            <CircleClose />
+          </template>
+        </el-popconfirm>
+      </el-icon>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.session-item {
+  /* 加一下padding不要让会话内容靠边界太近 */
+  padding: 12px;
+  background-color: white;
+  /* 给边界一些圆角看起来圆润一些 */
+  border-radius: 10px;
+  /* 固定宽度 */
+  width: 250px;
+  /* 当鼠标放在会话上时改变鼠标的样式,暗示用户可以点击。目前还没做拖动的效果,以后会做。 */
+  cursor: grab;
+  /* 父相子绝,父元素是相对布局的情况下,子元素的绝对布局是相当于父元素绝对布局。 */
+  position: relative;
+  /* 子元素的遮罩一开始会在外面,让溢出的遮罩不显示 */
+  overflow: hidden;
+
+  .name {
+    /* 会话名称字体要大一些 */
+    font-size: 14px;
+    /* 凸显名称,加粗 */
+    font-weight: 700;
+    width: 200px;
+    /* 加粗后颜色淡一些 */
+    color: rgba(black, 0.8);
+  }
+
+  .count-time {
+    /* 增加一些距离 */
+    margin-top: 10px;
+    /* 让字体小一些不能比会话名称要大(14px) */
+    font-size: 10px;
+    color: rgba(black, 0.5);
+    /* 让消息数量和最近更新时间显示水平显示 */
+    display: flex;
+    /* 让消息数量和最近更新时间分布在水平方向的两端 */
+    justify-content: space-between;
+  }
+
+  /* 当处于激活状态时增加蓝色描边 */
+  &.active {
+    /* 增加一些过渡 */
+    transition: all 0.12s linear;
+    border: 2px solid #1d93ab;
+  }
+
+  /* 当鼠标放在会话上时触发下面的css样式*/
+  &:hover {
+    /* 遮罩入场,从最左侧滑进去,渐渐变得不透明 */
+    .mask {
+      opacity: 1;
+      left: 0;
+    }
+
+    .btn-wrapper {
+      /* 暗示用户这个按钮可以点击 */
+      &:hover {
+        cursor: pointer;
+      }
+
+      /* 按钮入场,从最右侧滑进去,渐渐变得不透明 */
+      opacity: 1;
+      right: 20px;
+    }
+  }
+
+  .mask {
+    /* 渐变样式 */
+    transition: all 0.2s ease-out;
+    /* 相当于父亲绝对布局 */
+    position: absolute;
+    background-color: rgba(black, 0.05);
+    /* 和父亲元素一样宽盖住父元素 */
+    width: 100%;
+    /* 和父亲元素一样高 */
+    height: 100%;
+    /*位置移到父元素的最上面 */
+    top: 0;
+    /* 向父元素的最左侧再增加一个父亲元素当前宽度的距离 */
+    left: -100%;
+    /* 透明度为0 */
+    opacity: 0;
+  }
+
+  /* 删除按钮样式的逻辑和mask类似 */
+  .btn-wrapper {
+    color: rgba(black, 0.5);
+    transition: all 0.2s ease-out;
+    position: absolute;
+    top: 10px;
+    right: -20px;
+    z-index: 10;
+    opacity: 0;
+
+    .edit {
+      margin-right: 5px;
+    }
+  }
+}
+</style>

+ 50 - 0
front-end/src/views/chat/components/text-loading.vue

@@ -0,0 +1,50 @@
+<template>
+  <div class="loading">
+    <!--  三个 div 三个黑点 -->
+    <div></div>
+    <div></div>
+    <div></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.loading {
+  // 三个黑点水平展示
+  display: flex;
+  // 三个黑点均匀分布在54px中
+  justify-content: space-around;
+  color: #000;
+  width: 54px;
+  padding: 15px;
+
+  div {
+    background-color: currentColor;
+    border: 0 solid currentColor;
+    width: 5px;
+    height: 5px;
+    // 变成黑色圆点
+    border-radius: 100%;
+    // 播放我们下面定义的动画,每次动画持续0.7s且循环播放。
+    animation: ball-beat 0.7s -0.15s infinite linear;
+  }
+
+  div:nth-child(2n-1) {
+    // 慢0.5秒
+    animation-delay: -0.5s;
+  }
+}
+
+// 动画定义
+@keyframes ball-beat {
+  // 关键帧定义,在50%的时候是颜色变透明,且缩小。
+  50% {
+    opacity: 0.2;
+    transform: scale(0.75);
+  }
+  // 在100%时是回到正常状态,浏览器会自动在这两个关键帧间平滑过渡。
+  100% {
+    opacity: 1;
+    transform: scale(1);
+  }
+}
+</style>

+ 69 - 0
front-end/src/views/chat/store/chat-store.ts

@@ -0,0 +1,69 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import type { AiSessionDto } from '@/apis/__generated/model/dto'
+import { api } from '@/utils/api-instance'
+import type { AiMessageInput, AiSessionInput } from '@/apis/__generated/model/static'
+import { ElMessageBox } from 'element-plus'
+
+export type AiSession = Pick<
+  AiSessionDto['AiSessionRepository/FETCHER'],
+  'id' | 'name' | 'editedTime'
+> & {
+  messages: AiMessage[]
+}
+
+export type AiMessage = Pick<AiMessageInput, 'textContent' | 'medias' | 'type' | 'sessionId'> & {
+  id: string
+}
+export const useChatStore = defineStore('ai-chat', () => {
+  const isEdit = ref(false)
+  const activeSession = ref<AiSession>()
+  const sessionList = ref<AiSession[]>([])
+  const handleCreateSession = async (session: AiSessionInput) => {
+    const res = await api.aiSessionController.save({ body: session })
+    const sessionRes = await api.aiSessionController.findById({ id: res })
+    sessionList.value.unshift(sessionRes)
+    activeSession.value = sessionList.value[0]
+  }
+  // 从会话列表中删除会话
+  const handleDeleteSession = async (session: AiSession) => {
+    await api.aiSessionController.delete({ body: [session.id] })
+    const index = sessionList.value.findIndex((value) => {
+      return value.id === session.id
+    })
+    sessionList.value.splice(index, 1)
+    if (index == sessionList.value.length) {
+      activeSession.value = sessionList.value[index - 1]
+    } else {
+      activeSession.value = sessionList.value[index]
+    }
+  }
+  // 修改会话
+  const handleUpdateSession = async () => {
+    if (!activeSession.value) {
+      return
+    }
+    await api.aiSessionController.save({
+      body: { ...activeSession.value }
+    })
+    isEdit.value = false
+  }
+  const handleClearMessage = async (sessionId: string) => {
+    await ElMessageBox.confirm('是否清空会话记录?', '提示')
+    await api.aiMessageController.deleteHistory({ sessionId })
+    const index = sessionList.value.findIndex((value) => {
+      return value.id === sessionId
+    })
+    activeSession.value = await api.aiSessionController.findById({ id: sessionId })
+    sessionList.value[index] = activeSession.value
+  }
+  return {
+    isEdit,
+    activeSession,
+    sessionList,
+    handleUpdateSession,
+    handleCreateSession,
+    handleDeleteSession,
+    handleClearMessage
+  }
+})

+ 57 - 0
front-end/src/views/code/analyze/analyze-result-view.vue

@@ -0,0 +1,57 @@
+<script setup lang="ts">
+import { onMounted, ref } from 'vue'
+import { SSE } from 'sse.js'
+import CollapseTitle from './collapse-title.vue'
+import MarkdownMessage from '@/views/chat/components/markdown-message.vue'
+
+const props = defineProps<{ path: string }>()
+const API_PREFIX = import.meta.env.VITE_API_PREFIX
+type AnalyzeResult = {
+  id: string
+  content: string
+  fileName: string
+  fileContent: string
+}
+const resultMap = ref<Record<string, AnalyzeResult>>({})
+onMounted(() => {
+  const evtSource = new SSE(API_PREFIX + '/analyze?path=' + props.path, {
+    withCredentials: true,
+    // 禁用自动启动,需要调用stream()方法才能发起请求
+    start: false,
+    method: 'GET'
+  })
+  evtSource.addEventListener('message', async (event: any) => {
+    const result = JSON.parse(event.data) as AnalyzeResult
+    if (!resultMap.value[result.id]) {
+      resultMap.value[result.id] = result
+    } else {
+      resultMap.value[result.id].content += result.content
+    }
+  })
+  evtSource.stream()
+})
+const fileToMarkdown = (result: AnalyzeResult) => {
+  const content = result.fileContent.substring(1, result.fileContent.length - 1).replace(/\\/g, '')
+  return '```java\n' + content + '\n```'
+}
+</script>
+
+<template>
+  <div class="task-view">
+    <el-collapse>
+      <el-collapse-item :title="key" v-for="key in Object.keys(resultMap)" :key="key">
+        <template #title>
+          <collapse-title :title="key"></collapse-title>
+        </template>
+        <markdown-message :message="fileToMarkdown(resultMap[key])"></markdown-message>
+        <markdown-message :message="resultMap[key].content"></markdown-message>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.task-view {
+  padding: 20px;
+}
+</style>

+ 39 - 0
front-end/src/views/code/analyze/collapse-title.vue

@@ -0,0 +1,39 @@
+<script setup lang="ts">
+import type { ButtonProps } from 'element-plus/es/components/button/src/button'
+
+defineProps<{
+  title: string
+  icon?: string
+  type?: ButtonProps['type']
+}>()
+</script>
+
+<template>
+  <div class="title-wrapper">
+    <div class="right-icon"></div>
+    <div class="title">{{ title }}</div>
+    <el-button :type="type" :icon="icon" v-if="icon" plain link></el-button>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.title-wrapper {
+  display: flex;
+  align-items: center;
+  color: var(--el-color-primary);
+  width: 100%;
+
+  .title {
+    &:hover {
+      cursor: copy;
+    }
+  }
+
+  .right-icon {
+    margin-right: 5px;
+    height: 14px;
+    width: 3px;
+    background-color: var(--el-color-primary);
+  }
+}
+</style>

+ 159 - 0
front-end/src/views/login/login-view.vue

@@ -0,0 +1,159 @@
+<script lang="ts" setup>
+import {
+  ElAvatar,
+  ElButton,
+  ElCard,
+  ElCol,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElRow,
+  type FormInstance,
+  type FormRules
+} from 'element-plus'
+import { onMounted, reactive, ref, Transition } from 'vue'
+import logo from '@/assets/logo.jpg'
+import router from '@/router'
+import background from '@/assets/background.jpg'
+import { api } from '@/utils/api-instance'
+import type { UserLoginInput } from '@/apis/__generated/model/static'
+
+const loginForm = reactive<UserLoginInput>({ phone: '', password: '' })
+const ruleFormRef = ref<FormInstance>()
+const rules = reactive<FormRules<typeof loginForm>>({
+  phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
+  password: [
+    { required: true, message: '请输入密码', trigger: 'blur' },
+    { max: 16, min: 6, message: '密码长度介于6,16' }
+  ]
+})
+const showPanel = ref(false)
+onMounted(() => {
+  setTimeout(() => {
+    showPanel.value = true
+  }, 1000)
+})
+const handleLogin = async () => {
+  const res = await api.userController.login({ body: loginForm })
+  localStorage.setItem('token', res.tokenValue)
+  await router.replace({ path: '/' })
+}
+</script>
+<template>
+  <div>
+    <img alt="背景图片" class="background" :src="background" />
+    <el-row class="panel-wrapper" justify="center" align="middle">
+      <el-col :xs="18" :sm="16" :md="14" :lg="10" :xl="10">
+        <transition name="el-zoom-in-top">
+          <el-card class="panel" v-if="showPanel">
+            <div class="content">
+              <div class="panel-left">
+                <el-avatar alt="logo" :size="30" shape="square" :src="logo"></el-avatar>
+                <div class="title">AI助手</div>
+                <div class="description">构建你的AI助手</div>
+              </div>
+              <div class="panel-right">
+                <div class="title">快速开始</div>
+                <div class="description">登录你的账号</div>
+                <el-form
+                  ref="ruleFormRef"
+                  :model="loginForm"
+                  :rules="rules"
+                  class="form"
+                  label-position="top"
+                  label-width="100px"
+                >
+                  <el-form-item label="手机号">
+                    <el-input v-model="loginForm.phone"></el-input>
+                  </el-form-item>
+                  <el-form-item label="密码">
+                    <el-input v-model="loginForm.password" type="password"></el-input>
+                  </el-form-item>
+                </el-form>
+                <div class="button-wrapper">
+                  <el-button class="login" type="primary" @click="handleLogin"> 登录</el-button>
+                  <el-button
+                    class="register"
+                    type="info"
+                    size="small"
+                    link
+                    @click="() => router.push('/register')"
+                  >
+                    注册
+                  </el-button>
+                </div>
+              </div>
+            </div>
+          </el-card>
+        </transition>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+<style lang="scss" scoped>
+.background {
+  position: fixed;
+  height: 100vh;
+  width: 100vw;
+  object-fit: cover;
+  z-index: -10;
+}
+
+.panel-wrapper {
+  height: 100vh;
+
+  .panel {
+    .content {
+      display: flex;
+      align-items: stretch;
+      height: 50vh;
+
+      .title {
+        font-size: var(--el-font-size-extra-large);
+        margin-top: 16px;
+        font-weight: bold;
+      }
+
+      .description {
+        margin-top: 20px;
+        font-size: var(--el-font-size-base);
+        color: var(--el-text-col);
+      }
+
+      .panel-left {
+        box-sizing: border-box;
+        padding: 30px;
+        background-color: rgb(243, 245, 249);
+        width: 50%;
+        border-radius: 5px;
+      }
+
+      .panel-right {
+        padding: 30px;
+        width: 50%;
+
+        .form {
+          margin-top: 30px;
+        }
+
+        .button-wrapper {
+          margin-top: 40px;
+          display: flex;
+          justify-content: center;
+          position: relative;
+
+          .login {
+            width: 120px;
+          }
+
+          .register {
+            position: absolute;
+            right: 0;
+            bottom: 0;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 168 - 0
front-end/src/views/login/register-view.vue

@@ -0,0 +1,168 @@
+<script lang="ts" setup>
+import {
+  ElAvatar,
+  ElButton,
+  ElCard,
+  ElCol,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElRow,
+  type FormInstance,
+  type FormRules
+} from 'element-plus'
+import { reactive, ref } from 'vue'
+import logo from '@/assets/logo.jpg'
+import router from '@/router'
+import background from '@/assets/background.jpg'
+import { api } from '@/utils/api-instance'
+import type { UserRegisterInput } from '@/apis/__generated/model/static'
+import { assertFormValidate } from '@/utils/common'
+
+const registerForm = reactive<UserRegisterInput>({ phone: '', password: '' })
+const ruleFormRef = ref<FormInstance>()
+
+const rules = reactive<FormRules<typeof registerForm>>({
+  phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
+  password: [
+    { required: true, message: '请输入密码', trigger: 'blur' },
+    { max: 16, min: 6, message: '密码长度介于6,16' }
+  ]
+})
+const handleRegister = async () => {
+  if (!ruleFormRef.value) return
+  await ruleFormRef.value.validate(
+    assertFormValidate(() =>
+      api.userController.register({ body: registerForm }).then((res) => {
+        router.replace({ path: '/' })
+        localStorage.setItem('token', res.tokenValue)
+      })
+    )
+  )
+}
+</script>
+<template>
+  <div>
+    <img alt="背景图片" class="background" :src="background" />
+    <el-row class="panel-wrapper" justify="center" align="middle">
+      <el-col :xs="18" :sm="16" :md="14" :lg="10" :xl="10">
+        <transition name="el-zoom-in-top">
+          <el-card class="panel">
+            <div class="content">
+              <div class="panel-left">
+                <el-avatar alt="logo" :size="30" shape="square" :src="logo"></el-avatar>
+                <div class="title">AI助手</div>
+                <div class="description">构建你的AI助手</div>
+              </div>
+              <div class="panel-right">
+                <div class="title">快速开始</div>
+                <div class="description">创建你的账号</div>
+                <el-form
+                  ref="ruleFormRef"
+                  :model="registerForm"
+                  :rules="rules"
+                  class="form"
+                  label-position="top"
+                  label-width="100px"
+                >
+                  <el-form-item label="手机号" prop="phone">
+                    <el-input v-model="registerForm.phone"></el-input>
+                  </el-form-item>
+                  <el-form-item label="密码" prop="password">
+                    <el-input v-model="registerForm.password" type="password"></el-input>
+                  </el-form-item>
+                </el-form>
+                <div class="button-wrapper">
+                  <el-button class="register" type="primary" @click="handleRegister">
+                    注册
+                  </el-button>
+                  <el-button class="login" size="small" link @click="router.replace('/login')">
+                    登录
+                  </el-button>
+                </div>
+              </div>
+            </div>
+          </el-card>
+        </transition>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.background {
+  position: fixed;
+  height: 100vh;
+  width: 100vw;
+  object-fit: cover;
+  z-index: -10;
+}
+
+.panel-wrapper {
+  height: 100vh;
+
+  .panel {
+    .content {
+      display: flex;
+      align-items: stretch;
+      height: 50vh;
+
+      .title {
+        font-size: var(--el-font-size-extra-large);
+        margin-top: 16px;
+        font-weight: bold;
+      }
+
+      .description {
+        margin-top: 20px;
+        font-size: var(--el-font-size-base);
+        color: var(--el-text-col);
+      }
+
+      .panel-left {
+        box-sizing: border-box;
+        padding: 30px;
+        background-color: rgb(243, 245, 249);
+        width: 50%;
+        border-radius: 5px;
+      }
+
+      .panel-right {
+        padding: 30px;
+        width: 50%;
+
+        .form {
+          margin-top: 30px;
+
+          .sms {
+            display: flex;
+            align-items: center;
+            width: 100%;
+
+            .send-sms {
+              margin-left: 20px;
+            }
+          }
+        }
+
+        .button-wrapper {
+          margin-top: 40px;
+          display: flex;
+          justify-content: center;
+          position: relative;
+
+          .register {
+            width: 120px;
+          }
+
+          .login {
+            position: absolute;
+            right: 0;
+            bottom: 0;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 14 - 0
front-end/tsconfig.app.json

@@ -0,0 +1,14 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.dom.json",
+  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
+  "exclude": ["src/**/__tests__/*"],
+  "compilerOptions": {
+    "composite": true,
+    "noEmit": true,
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["./src/*"]
+    },
+    "types": ["element-plus/global"]
+  }
+}

+ 11 - 0
front-end/tsconfig.json

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

+ 17 - 0
front-end/tsconfig.node.json

@@ -0,0 +1,17 @@
+{
+  "extends": "@tsconfig/node18/tsconfig.json",
+  "include": [
+    "vite.config.*",
+    "vitest.config.*",
+    "cypress.config.*",
+    "nightwatch.conf.*",
+    "playwright.config.*"
+  ],
+  "compilerOptions": {
+    "composite": true,
+    "noEmit": true,
+    "module": "ESNext",
+    "moduleResolution": "Bundler",
+    "types": ["node"]
+  }
+}

+ 27 - 0
front-end/vite.config.ts

@@ -0,0 +1,27 @@
+import { fileURLToPath, URL } from 'node:url'
+
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [vue(), vueJsx()],
+  resolve: {
+    alias: {
+      '@': fileURLToPath(new URL('./src', import.meta.url))
+    }
+  },
+  server: {
+    host: '0.0.0.0',
+    port: 5177,
+    proxy: {
+      '/api': {
+        //target: 'http://localhost:9902',
+        target: 'http://58.87.69.234:9902',
+        changeOrigin: true,
+        rewrite: (path) => path.replace(/^\/api/, '')
+      }
+    }
+  }
+})

+ 210 - 0
pom.xml

@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>io.github.qifan777</groupId>
+    <artifactId>dive-into-spring-ai</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <parent>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <groupId>org.springframework.boot</groupId>
+        <relativePath/>
+        <version>3.2.1</version>
+    </parent>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <spring-ai.version>1.0.0-M5</spring-ai.version>
+        <spring-ai-alibaba.version>1.0.0-M5.1</spring-ai-alibaba.version>
+        <uni-ai.version>0.1.9</uni-ai.version>
+        <jimmer.version>0.8.134</jimmer.version>
+        <hutool.version>5.8.25</hutool.version>
+        <sa-token.version>1.37.0</sa-token.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.javaparser</groupId>
+            <artifactId>javaparser-symbol-solver-core</artifactId>
+            <version>3.26.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>3.3.4</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-io</groupId>
+                    <artifactId>commons-io</artifactId>
+                </exclusion>
+
+                <exclusion>
+                    <groupId>org.apache.commons</groupId>
+                    <artifactId>commons-compress</artifactId>
+                </exclusion>
+
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-neo4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.ai</groupId>
+            <artifactId>spring-ai-redis-store</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.ai</groupId>
+            <artifactId>spring-ai-tika-document-reader</artifactId>
+
+        </dependency>
+        <dependency>
+            <artifactId>jimmer-spring-boot-starter</artifactId>
+            <groupId>org.babyfish.jimmer</groupId>
+            <version>${jimmer.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.github.qifan777</groupId>
+            <artifactId>spring-boot-starter-oss</artifactId>
+        </dependency>
+        <!--      根据自己的需要开启响应厂商的配置-->
+        <!--        <dependency>-->
+        <!--            <groupId>org.springframework.ai</groupId>-->
+        <!--            <artifactId>spring-ai-moonshot-spring-boot-starter</artifactId>-->
+        <!--        </dependency>-->
+        <!--        <dependency>-->
+        <!--            <groupId>org.springframework.ai</groupId>-->
+        <!--            <artifactId>spring-ai-qianfan-spring-boot-starter</artifactId>-->
+        <!--        </dependency>-->
+        <!--        <dependency>-->
+        <!--            <groupId>org.springframework.ai</groupId>-->
+        <!--            <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>-->
+        <!--        </dependency>-->
+        <!--        <dependency>-->
+        <!--            <groupId>org.springframework.ai</groupId>-->
+        <!--            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>-->
+        <!--        </dependency>-->
+        <!--        <dependency>-->
+        <!--            <groupId>org.springframework.ai</groupId>-->
+        <!--            <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>-->
+        <!--        </dependency>-->
+        <dependency>
+            <groupId>com.alibaba.cloud.ai</groupId>
+            <artifactId>spring-ai-alibaba-starter</artifactId>
+            <version>${spring-ai-alibaba.version}</version>
+        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>org.springframework.ai</groupId>-->
+<!--            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>-->
+<!--        </dependency>-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+        </dependency>
+        <dependency>
+            <artifactId>mysql-connector-j</artifactId>
+            <groupId>com.mysql</groupId>
+        </dependency>
+        <dependency>
+            <artifactId>hutool-core</artifactId>
+            <groupId>cn.hutool</groupId>
+            <version>${hutool.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-redis-jackson</artifactId>
+            <version>${sa-token.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-spring-boot3-starter</artifactId>
+            <version>${sa-token.version}</version>
+        </dependency>
+    </dependencies>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.ai</groupId>
+                <artifactId>spring-ai-bom</artifactId>
+                <version>${spring-ai.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>io.github.qifan777</groupId>
+                <artifactId>uni-ai-bom</artifactId>
+                <version>${uni-ai.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+    <repositories>
+        <repository>
+            <id>spring-milestones</id>
+            <name>Spring Milestones</name>
+            <url>https://repo.spring.io/milestone</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+        <repository>
+            <id>spring-snapshots</id>
+            <name>Spring Snapshots</name>
+            <url>https://repo.spring.io/snapshot</url>
+            <releases>
+                <enabled>false</enabled>
+            </releases>
+        </repository>
+    </repositories>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <annotationProcessorPaths>
+                        <path>
+                            <artifactId>jimmer-apt</artifactId>
+                            <groupId>org.babyfish.jimmer</groupId>
+                            <version>${jimmer.version}</version>
+                        </path>
+                        <path>
+                            <artifactId>lombok</artifactId>
+                            <groupId>org.projectlombok</groupId>
+                            <version>${lombok.version}</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 6 - 0
src/main/dto/AiMessage.dto

@@ -0,0 +1,6 @@
+export io.github.qifan777.knowledge.ai.message.AiMessage
+
+input AiMessageInput{
+    #allScalars(AiMessage)
+    id(session)
+}

+ 6 - 0
src/main/dto/AiSession.dto

@@ -0,0 +1,6 @@
+export io.github.qifan777.knowledge.ai.session.AiSession
+
+input AiSessionInput{
+    id?
+    name
+}

+ 10 - 0
src/main/dto/User.dto

@@ -0,0 +1,10 @@
+export io.github.qifan777.knowledge.user.User
+
+input UserLoginInput{
+    phone
+    password
+}
+input UserRegisterInput{
+    phone
+    password
+}

+ 17 - 0
src/main/java/io/github/qifan777/knowledge/ServerApplication.java

@@ -0,0 +1,17 @@
+package io.github.qifan777.knowledge;
+
+
+import io.github.qifan777.knowledge.infrastructure.code.CodeAssistantProperties;
+import org.babyfish.jimmer.client.EnableImplicitApi;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+@SpringBootApplication
+@EnableImplicitApi
+@EnableConfigurationProperties(CodeAssistantProperties.class)
+public class ServerApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(ServerApplication.class, args);
+    }
+}

+ 36 - 0
src/main/java/io/github/qifan777/knowledge/ai/agent/AbstractAgent.java

@@ -0,0 +1,36 @@
+package io.github.qifan777.knowledge.ai.agent;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Description;
+import org.springframework.util.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Slf4j
+public abstract class AbstractAgent {
+
+    /**
+     * 获取指定的function bean名称
+     *
+     * @return Function Call名称列表
+     */
+    public String[] getFunctions(Class<?>... clazz) {
+        List<Class<?>> classList = Arrays.stream(clazz).filter(aClass -> aClass.isAnnotationPresent(Description.class)).toList();
+        String[] names = new String[classList.size()];
+        classList.stream().map(aClass -> StringUtils.uncapitalize(aClass.getSimpleName())).toList().toArray(names);
+        return names;
+    }
+    /**
+     * 获取内嵌的Function Call也就是Agent的Tools
+     *
+     * @return Function Call名称列表
+     */
+    public String[] getAgentFunctions(Class<?> clazz) {
+        List<Class<?>> classList = Arrays.stream(clazz.getClasses()).filter(aClass -> aClass.isAnnotationPresent(Description.class)).toList();
+        String[] names = new String[classList.size()];
+        classList.stream().map(aClass -> StringUtils.uncapitalize(this.getClass().getSimpleName()) + "." + aClass.getSimpleName()).toList().toArray(names);
+        return names;
+    }
+
+}

+ 14 - 0
src/main/java/io/github/qifan777/knowledge/ai/agent/Agent.java

@@ -0,0 +1,14 @@
+package io.github.qifan777.knowledge.ai.agent;
+
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Component
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Agent {
+}

+ 70 - 0
src/main/java/io/github/qifan777/knowledge/ai/agent/chronologist/Chronologist.java

@@ -0,0 +1,70 @@
+package io.github.qifan777.knowledge.ai.agent.chronologist;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import io.github.qifan777.knowledge.ai.agent.AbstractAgent;
+import io.github.qifan777.knowledge.ai.agent.Agent;
+import lombok.AllArgsConstructor;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.context.annotation.Description;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.function.Function;
+
+@Agent
+@Description(value = "回答用户有关于日期、时间的提问")
+@AllArgsConstructor
+public class Chronologist extends AbstractAgent implements Function<Chronologist.Request, String> {
+    private final String SYSTEM = """
+            你是一个专业的编年史学家,可以回答有关时间的问题。
+            您还可以执行各种与时间相关的任务,如转换和格式化。
+            """;
+    private final ChatModel chatModel;
+
+
+    @Override
+    public String apply(Request request) {
+        return ChatClient.create(chatModel)
+                .prompt()
+                .system(SYSTEM)
+                .user(request.query)
+                .functions(getAgentFunctions(this.getClass()))
+                .call()
+                .content();
+    }
+
+    public record Request(
+            @JsonProperty(required = true) @JsonPropertyDescription(value = "用户原始的提问") String query) {
+    }
+
+    @Component
+    @Description("获取当前的时间,格式是 HH:mm:ss")
+    public static class CurrentTime implements Function<CurrentTime.Request, String> {
+        @Override
+        public String apply(Request request) {
+            LocalDateTime currentDate = LocalDateTime.now();
+            return currentDate.toLocalTime().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
+
+        }
+
+        public record Request() {
+        }
+    }
+
+    @Component
+    @Description("获取当前的日期,格式是 yyyy-MM-dd")
+    public static class CurrentDate implements Function<CurrentDate.Request, String> {
+        @Override
+        public String apply(Request request) {
+            LocalDate currentDate = LocalDate.now();
+            return currentDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        }
+
+        public record Request() {
+        }
+    }
+}

+ 35 - 0
src/main/java/io/github/qifan777/knowledge/ai/agent/computer/ComputerAssistant.java

@@ -0,0 +1,35 @@
+package io.github.qifan777.knowledge.ai.agent.computer;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import io.github.qifan777.knowledge.ai.agent.AbstractAgent;
+import io.github.qifan777.knowledge.ai.agent.Agent;
+import lombok.AllArgsConstructor;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.context.annotation.Description;
+
+import java.util.function.Function;
+
+@Agent
+@Description("提供关于当前主机的cpu,文件,文件夹相关问题的有用回答")
+@AllArgsConstructor
+public class ComputerAssistant extends AbstractAgent implements Function<ComputerAssistant.Request, String> {
+    private final ChatModel chatModel;
+
+    @Override
+    public String apply(Request request) {
+        return ChatClient.create(chatModel)
+                .prompt()
+                .functions(getFunctions(CpuAnalyzer.class, DirectoryReader.class))
+                .user(request.query())
+                .call()
+                .content();
+    }
+
+    public record Request(
+            @JsonProperty(required = true) @JsonPropertyDescription(value = "用户原始的提问") String query) {
+    }
+
+
+}

+ 18 - 0
src/main/java/io/github/qifan777/knowledge/ai/agent/computer/CpuAnalyzer.java

@@ -0,0 +1,18 @@
+package io.github.qifan777.knowledge.ai.agent.computer;
+
+import org.springframework.context.annotation.Description;
+import org.springframework.stereotype.Component;
+
+import java.util.function.Function;
+
+@Component
+@Description("读取CPU的数量")
+public class CpuAnalyzer implements Function<CpuAnalyzer.Request, Integer> {
+    @Override
+    public Integer apply(Request request) {
+        return Runtime.getRuntime().availableProcessors();
+    }
+
+    public record Request() {
+    }
+}

+ 34 - 0
src/main/java/io/github/qifan777/knowledge/ai/agent/computer/DirectoryReader.java

@@ -0,0 +1,34 @@
+package io.github.qifan777.knowledge.ai.agent.computer;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import org.springframework.context.annotation.Description;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+@Component
+@Description("读取用户给定的文件夹,列出文件夹下的所有文件")
+public class DirectoryReader implements Function<DirectoryReader.Request, String> {
+    @Override
+    public String apply(Request request) {
+        File f = new File(request.path);
+        List<String> out = new ArrayList<>();
+        if (f.exists()) {
+            String[] list = f.list();
+            if (list != null) {
+                out = Arrays.asList(list);
+            }
+        }
+        return String.join(",", out);
+    }
+
+    public record Request(
+            @JsonProperty(required = true) @JsonPropertyDescription("本机文件夹的绝对路径") String path
+    ) {
+    }
+}

+ 43 - 0
src/main/java/io/github/qifan777/knowledge/ai/document/DocumentController.java

@@ -0,0 +1,43 @@
+package io.github.qifan777.knowledge.ai.document;
+
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.reader.tika.TikaDocumentReader;
+import org.springframework.ai.transformer.splitter.TokenTextSplitter;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+@RequestMapping("document")
+@RestController
+@AllArgsConstructor
+public class DocumentController {
+    private final VectorStore vectorStore;
+
+    /**
+     * 嵌入文件
+     *
+     * @param file 待嵌入的文件
+     * @return 是否成功
+     */
+    @SneakyThrows
+    @PostMapping("embedding")
+    public Boolean embedding(@RequestParam MultipartFile file) {
+        // 从IO流中读取文件
+        TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(new InputStreamResource(file.getInputStream()));
+        // 将文本内容划分成更小的块
+        List<Document> splitDocuments = new TokenTextSplitter()
+            .apply(tikaDocumentReader.read());
+        // 存入向量数据库,这个过程会自动调用embeddingModel,将文本变成向量再存入。
+        vectorStore.add(splitDocuments);
+        return true;
+    }
+
+}

+ 53 - 0
src/main/java/io/github/qifan777/knowledge/ai/message/AiMessage.java

@@ -0,0 +1,53 @@
+package io.github.qifan777.knowledge.ai.message;
+
+import io.github.qifan777.knowledge.ai.session.AiSession;
+import io.github.qifan777.knowledge.infrastructure.jimmer.BaseEntity;
+import jakarta.validation.constraints.Null;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.babyfish.jimmer.sql.*;
+import org.springframework.ai.chat.messages.MessageType;
+
+import java.util.List;
+
+/**
+ * 历史消息
+ */
+
+@Entity
+public interface AiMessage extends BaseEntity {
+
+    /**
+     * 消息类型(用户/助手/系统)
+     */
+    MessageType type();
+
+    /**
+     * 消息内容
+     */
+    String textContent();
+
+    @Serialized
+    @Null
+    List<Media> medias();
+
+    @IdView
+    String sessionId();
+
+    /**
+     * 会话
+     */
+    @ManyToOne
+    @JoinColumn(name = "ai_session_id")
+    @OnDissociate(DissociateAction.DELETE)
+    AiSession session();
+
+    @Data
+    @Accessors(chain = true)
+    class Media {
+        public String type;
+        public String data;
+    }
+}
+

+ 98 - 0
src/main/java/io/github/qifan777/knowledge/ai/message/AiMessageChatMemory.java

@@ -0,0 +1,98 @@
+package io.github.qifan777.knowledge.ai.message;
+
+import cn.hutool.core.collection.CollectionUtil;
+import io.qifan.infrastructure.common.exception.BusinessException;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springframework.ai.chat.memory.ChatMemory;
+import org.springframework.ai.chat.messages.*;
+import org.springframework.ai.model.Media;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+@AllArgsConstructor
+public class AiMessageChatMemory implements ChatMemory {
+    private final AiMessageRepository messageRepository;
+
+    public static AiMessage toAiMessage(Message message, String sessionId) {
+        return AiMessageDraft.$.produce(draft -> {
+            draft.setSessionId(sessionId)
+                    .setTextContent(message.getText())
+                    .setType(message.getMessageType())
+                    .setMedias(new ArrayList<>());
+            if (message instanceof UserMessage userMessage &&
+                !CollectionUtil.isEmpty(userMessage.getMedia())) {
+                List<AiMessage.Media> mediaList = userMessage
+                        .getMedia()
+                        .stream()
+                        .map(media -> new AiMessage.Media()
+                                .setType(media.getMimeType().getType())
+                                .setData(media.getData().toString()))
+                        .toList();
+                draft.setMedias(mediaList);
+            }
+        });
+    }
+
+    public static Message toSpringAiMessage(AiMessage aiMessage) {
+        List<Media> mediaList = new ArrayList<>();
+        if (!CollectionUtil.isEmpty(aiMessage.medias())) {
+            mediaList = aiMessage.medias().stream().map(AiMessageChatMemory::toSpringAiMedia).toList();
+        }
+        if (aiMessage.type().equals(MessageType.ASSISTANT)) {
+            return new AssistantMessage(aiMessage.textContent());
+        }
+        if (aiMessage.type().equals(MessageType.USER)) {
+            return new UserMessage(aiMessage.textContent(), mediaList);
+        }
+        if (aiMessage.type().equals(MessageType.SYSTEM)) {
+            return new SystemMessage(aiMessage.textContent());
+        }
+        throw new BusinessException("不支持的消息类型");
+    }
+
+    @SneakyThrows
+    public static Media toSpringAiMedia(AiMessage.Media media) {
+        return new Media(new MediaType(media.getType()), new URL(media.getData()));
+    }
+
+    /**
+     * 不实现,手动前端发起请求保存用户的消息和大模型回复的消息
+     */
+    @Override
+    public void add(String conversationId, List<Message> messages) {
+    }
+
+    /**
+     * 查询会话内的消息最新n条历史记录
+     *
+     * @param conversationId 会话id
+     * @param lastN          最近n条
+     * @return org.springframework.ai.chat.messages.Message格式的消息
+     */
+    @Override
+    public List<Message> get(String conversationId, int lastN) {
+        return messageRepository
+                // 查询会话内的最新n条消息
+                .findBySessionId(conversationId, lastN)
+                .stream()
+                // 转成Message对象
+                .map(AiMessageChatMemory::toSpringAiMessage)
+                .toList();
+    }
+
+    /**
+     * 清除会话内的消息
+     *
+     * @param conversationId 会话id
+     */
+    @Override
+    public void clear(String conversationId) {
+        messageRepository.deleteBySessionId(conversationId);
+    }
+}

+ 163 - 0
src/main/java/io/github/qifan777/knowledge/ai/message/AiMessageController.java

@@ -0,0 +1,163 @@
+package io.github.qifan777.knowledge.ai.message;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.github.qifan777.knowledge.ai.agent.Agent;
+import io.github.qifan777.knowledge.ai.message.dto.AiMessageInput;
+import io.github.qifan777.knowledge.ai.message.dto.AiMessageWrapper;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
+import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor;
+import org.springframework.ai.chat.messages.Message;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.prompt.PromptTemplate;
+import org.springframework.ai.model.Media;
+import org.springframework.ai.reader.tika.TikaDocumentReader;
+import org.springframework.ai.vectorstore.SearchRequest;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.http.MediaType;
+import org.springframework.http.codec.ServerSentEvent;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import reactor.core.publisher.Flux;
+
+import java.util.Map;
+
+@RequestMapping("message")
+@RestController
+@AllArgsConstructor
+@Slf4j
+public class AiMessageController {
+    private final AiMessageChatMemory chatMemory;
+    private final ChatModel chatModel;
+    //    private final ImageModel imageModel;
+    private final VectorStore vectorStore;
+    private final ObjectMapper objectMapper;
+    private final AiMessageRepository messageRepository;
+    private final ApplicationContext applicationContext;
+
+    @DeleteMapping("history/{sessionId}")
+    public void deleteHistory(@PathVariable String sessionId) {
+        chatMemory.clear(sessionId);
+    }
+
+    /**
+     * 消息保存
+     *
+     * @param input 用户发送的消息/AI回复的消息
+     */
+    @PostMapping
+    public void save(@RequestBody AiMessageInput input) {
+        messageRepository.save(input.toEntity());
+    }
+
+//    @PostMapping("chat/image")
+//    public String textToImageChat(@RequestBody AiMessageInput input) {
+//        return imageModel.call(new ImagePrompt(input.getTextContent())).getResult().getOutput().getUrl();
+//    }
+
+    /**
+     * 为了支持文件问答,需要同时接收json(AiMessageWrapper json体)和 MultipartFile(文件)
+     * Content-Type 从 application/json 修改为 multipart/form-data
+     * 之前接收请求参数是用@RequestBody, 现在使用@RequestPart 接收json字符串再手动转成AiMessageWrapper.
+     * SpringMVC的@RequestPart是支持自动将Json字符串转换为Java对象,也就是说可以等效`@RequestBody`,
+     * 但是由于前端FormData无法设置Part的Content-Type,所以只能手动转json字符串再转成Java对象。
+     *
+     * @param input 消息包含文本信息,会话id,多媒体信息(图片语言)。参考src/main/dto/AiMessage.dto
+     * @param file  文件问答
+     * @return SSE流
+     */
+    @SneakyThrows
+    @PostMapping(value = "chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    public Flux<ServerSentEvent<String>> chat(@RequestPart String input, @RequestPart(required = false) MultipartFile file) {
+        AiMessageWrapper aiMessageWrapper = objectMapper.readValue(input, AiMessageWrapper.class);
+        String[] functionBeanNames = new String[0];
+        // 如果启用Agent则获取Agent的bean
+        if (aiMessageWrapper.getParams().getEnableAgent()) {
+            // 获取带有Agent注解的bean
+            Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(Agent.class);
+            functionBeanNames = new String[beansWithAnnotation.size()];
+            functionBeanNames = beansWithAnnotation.keySet().toArray(functionBeanNames);
+        }
+        return ChatClient.create(chatModel).prompt()
+                // 启用文件问答
+                .system(promptSystemSpec -> useFile(promptSystemSpec, file))
+                .user(promptUserSpec -> toPrompt(promptUserSpec, aiMessageWrapper.getMessage()))
+                // agent列表
+                .functions(functionBeanNames)
+                .advisors(advisorSpec -> {
+                    // 使用历史消息
+                    useChatHistory(advisorSpec, aiMessageWrapper.getMessage().getSessionId());
+                    // 使用向量数据库
+                    useVectorStore(advisorSpec, aiMessageWrapper.getParams().getEnableVectorStore());
+                })
+                .stream()
+                .chatResponse()
+                .map(chatResponse -> ServerSentEvent.builder(toJson(chatResponse))
+                        // 和前端监听的事件相对应
+                        .event("message")
+                        .build());
+    }
+
+    @SneakyThrows
+    public String toJson(ChatResponse response) {
+        return objectMapper.writeValueAsString(response);
+    }
+
+    public void toPrompt(ChatClient.PromptUserSpec promptUserSpec, AiMessageInput input) {
+        // AiMessageInput转成Message
+        Message message = AiMessageChatMemory.toSpringAiMessage(input.toEntity());
+        if (message instanceof UserMessage userMessage &&
+            !CollectionUtils.isEmpty(userMessage.getMedia())) {
+            // 用户发送的图片/语言
+            Media[] medias = new Media[userMessage.getMedia().size()];
+            promptUserSpec.media(userMessage.getMedia().toArray(medias));
+        }
+        // 用户发送的文本
+        promptUserSpec.text(message.getText());
+    }
+
+    public void useChatHistory(ChatClient.AdvisorSpec advisorSpec, String sessionId) {
+        // 1. 如果需要存储会话和消息到数据库,自己可以实现ChatMemory接口,这里使用自己实现的AiMessageChatMemory,数据库存储。
+        // 2. 传入会话id,MessageChatMemoryAdvisor会根据会话id去查找消息。
+        // 3. 只需要携带最近10条消息
+        // MessageChatMemoryAdvisor会在消息发送给大模型之前,从ChatMemory中获取会话的历史消息,然后一起发送给大模型。
+        advisorSpec.advisors(new MessageChatMemoryAdvisor(chatMemory, sessionId, 10));
+    }
+
+    public void useVectorStore(ChatClient.AdvisorSpec advisorSpec, Boolean enableVectorStore) {
+        if (!enableVectorStore) return;
+        // question_answer_context是一个占位符,会替换成向量数据库中查询到的文档。QuestionAnswerAdvisor会替换。
+        String promptWithContext = """
+                下面是上下文信息
+                ---------------------
+                {question_answer_context}
+                ---------------------
+                给定的上下文和提供的历史信息,而不是事先的知识,回复用户的意见。如果答案不在上下文中,告诉用户你不能回答这个问题。
+                """;
+        advisorSpec.advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.builder().build(), promptWithContext));
+    }
+
+    @SneakyThrows
+    public void useFile(ChatClient.PromptSystemSpec spec, MultipartFile file) {
+        if (file == null) return;
+        String content = new TikaDocumentReader(new InputStreamResource(file.getInputStream())).get().get(0).getText();
+        Message message = new PromptTemplate("""
+                已下内容是额外的知识,在你回答问题时可以参考下面的内容
+                ---------------------
+                {context}
+                ---------------------
+                """)
+                .createMessage(Map.of("context", content));
+        spec.text(message.getText());
+    }
+
+}

+ 25 - 0
src/main/java/io/github/qifan777/knowledge/ai/message/AiMessageRepository.java

@@ -0,0 +1,25 @@
+package io.github.qifan777.knowledge.ai.message;
+
+import org.babyfish.jimmer.spring.repository.JRepository;
+
+import java.util.List;
+
+public interface AiMessageRepository extends JRepository<AiMessage, String> {
+    AiMessageTable t = AiMessageTable.$;
+
+    default List<AiMessage> findBySessionId(String sessionId, int lastN) {
+        return sql()
+                .createQuery(t)
+                .where(t.sessionId().eq(sessionId))
+                .orderBy(t.createdTime().asc())
+                .select(t)
+                .limit(lastN)
+                .execute();
+    }
+
+    default Integer deleteBySessionId(String sessionId) {
+        return sql().createDelete(t)
+                .where(t.sessionId().eq(sessionId))
+                .execute();
+    }
+}

+ 10 - 0
src/main/java/io/github/qifan777/knowledge/ai/message/MessageInputWrapper.java

@@ -0,0 +1,10 @@
+package io.github.qifan777.knowledge.ai.message;
+
+import lombok.Data;
+
+@Data
+public class MessageInputWrapper {
+    AiMessage message;
+
+
+}

+ 9 - 0
src/main/java/io/github/qifan777/knowledge/ai/message/dto/AiMessageParams.java

@@ -0,0 +1,9 @@
+package io.github.qifan777.knowledge.ai.message.dto;
+
+import lombok.Data;
+
+@Data
+public class AiMessageParams {
+    Boolean enableVectorStore;
+    Boolean enableAgent;
+}

+ 9 - 0
src/main/java/io/github/qifan777/knowledge/ai/message/dto/AiMessageWrapper.java

@@ -0,0 +1,9 @@
+package io.github.qifan777.knowledge.ai.message.dto;
+
+import lombok.Data;
+
+@Data
+public class AiMessageWrapper {
+    AiMessageInput message;
+    AiMessageParams params;
+}

+ 29 - 0
src/main/java/io/github/qifan777/knowledge/ai/session/AiSession.java

@@ -0,0 +1,29 @@
+package io.github.qifan777.knowledge.ai.session;
+
+import io.github.qifan777.knowledge.ai.message.AiMessage;
+import io.github.qifan777.knowledge.infrastructure.jimmer.BaseEntity;
+import org.babyfish.jimmer.sql.Entity;
+import org.babyfish.jimmer.sql.OneToMany;
+import org.babyfish.jimmer.sql.OrderedProp;
+
+import java.util.List;
+
+/**
+ * 会话
+ */
+@Entity
+public interface AiSession extends BaseEntity {
+
+    /**
+     * 会话名称
+     */
+    String name();
+
+    /**
+     * 一对多关联消息,按创建时间升序
+     */
+
+    @OneToMany(mappedBy = "session", orderedProps = @OrderedProp(value = "createdTime"))
+    List<AiMessage> messages();
+}
+

+ 55 - 0
src/main/java/io/github/qifan777/knowledge/ai/session/AiSessionController.java

@@ -0,0 +1,55 @@
+package io.github.qifan777.knowledge.ai.session;
+
+import io.github.qifan777.knowledge.ai.session.dto.AiSessionInput;
+import io.qifan.infrastructure.common.exception.BusinessException;
+import lombok.AllArgsConstructor;
+import org.babyfish.jimmer.client.FetchBy;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RequestMapping("session")
+@RestController
+@AllArgsConstructor
+public class AiSessionController {
+    private final AiSessionRepository sessionRepository;
+
+    /**
+     * 根据id查询会话
+     * @param id 会话id
+     * @return 会话信息
+     */
+    @GetMapping("{id}")
+    public @FetchBy(value = "FETCHER", ownerType = AiSessionRepository.class) AiSession findById(@PathVariable String id) {
+        return sessionRepository.findById(id, AiSessionRepository.FETCHER).orElseThrow(() -> new BusinessException("会话不存在"));
+    }
+
+    /**
+     * 保存会话
+     * @param input 会话dto参考src/main/dto/AiSession.dto
+     * @return 创建后的id
+     */
+    @PostMapping("save")
+    public String save(@RequestBody AiSessionInput input) {
+        return sessionRepository.save(input.toEntity()).id();
+    }
+
+    /**
+     * 查询当前登录用户的会话
+     *
+     * @return 会话列表
+     */
+    @GetMapping("user")
+    public List<@FetchBy(value = "FETCHER", ownerType = AiSessionRepository.class) AiSession> findByUser() {
+        return sessionRepository.findByUser();
+    }
+
+    /**
+     * 批量删除会话
+     * @param ids 会话id列表
+     */
+    @DeleteMapping
+    public void delete(@RequestBody List<String> ids) {
+        sessionRepository.deleteByIds(ids);
+    }
+}

+ 20 - 0
src/main/java/io/github/qifan777/knowledge/ai/session/AiSessionRepository.java

@@ -0,0 +1,20 @@
+package io.github.qifan777.knowledge.ai.session;
+
+import cn.dev33.satoken.stp.StpUtil;
+import io.github.qifan777.knowledge.ai.message.AiMessageFetcher;
+import org.babyfish.jimmer.spring.repository.JRepository;
+
+import java.util.List;
+
+public interface AiSessionRepository extends JRepository<AiSession, String> {
+    AiSessionTable t = AiSessionTable.$;
+    AiSessionFetcher FETCHER = AiSessionFetcher.$.allScalarFields()
+            .messages(AiMessageFetcher.$.allScalarFields().sessionId());
+
+    default List<AiSession> findByUser() {
+        return sql().createQuery(t)
+                .where(t.creatorId().eq(StpUtil.getLoginIdAsString()))
+                .select(t.fetch(FETCHER))
+                .execute();
+    }
+}

+ 35 - 0
src/main/java/io/github/qifan777/knowledge/code/CodeAssistantAgent.java

@@ -0,0 +1,35 @@
+package io.github.qifan777.knowledge.code;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import io.github.qifan777.knowledge.ai.agent.AbstractAgent;
+import io.github.qifan777.knowledge.ai.agent.Agent;
+import io.github.qifan777.knowledge.code.analyze.AnalyzeFunction;
+import io.github.qifan777.knowledge.code.arthas.ArthasFunction;
+import lombok.AllArgsConstructor;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.context.annotation.Description;
+
+import java.util.function.Function;
+
+@Description("提供有关于Java代码的评审分析,在线诊断异常相关的回答")
+@Agent
+@AllArgsConstructor
+public class CodeAssistantAgent extends AbstractAgent implements Function<CodeAssistantAgent.Request, String> {
+    private final ChatModel chatModel;
+
+    @Override
+    public String apply(Request request) {
+        return ChatClient.create(chatModel)
+                .prompt()
+                .user(request.query())
+                .functions(getFunctions(AnalyzeFunction.class, ArthasFunction.class))
+                .call()
+                .content();
+    }
+
+    public record Request(
+            @JsonProperty(required = true) @JsonPropertyDescription(value = "用户原始的提问") String query) {
+    }
+}

+ 33 - 0
src/main/java/io/github/qifan777/knowledge/code/analyze/AnalyzeController.java

@@ -0,0 +1,33 @@
+package io.github.qifan777.knowledge.code.analyze;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springframework.http.MediaType;
+import org.springframework.http.codec.ServerSentEvent;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Flux;
+
+@RestController
+@RequestMapping("analyze")
+@AllArgsConstructor
+public class AnalyzeController {
+    private final AnalyzeFunction analyzeFunction;
+    private final ObjectMapper objectMapper;
+
+    @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    public Flux<ServerSentEvent<String>> analyzeTask(@RequestParam String path) {
+        return analyzeFunction.analyze(path)
+                .map(content -> ServerSentEvent.builder(toJson(content))
+                        .event("message")
+                        .build());
+    }
+
+    @SneakyThrows
+    public String toJson(AnalyzeFunction.AnalyzeResult result) {
+        return objectMapper.writeValueAsString(result);
+    }
+}

+ 144 - 0
src/main/java/io/github/qifan777/knowledge/code/analyze/AnalyzeFunction.java

@@ -0,0 +1,144 @@
+package io.github.qifan777.knowledge.code.analyze;
+
+import cn.hutool.core.io.FileUtil;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import io.github.qifan777.knowledge.code.graph.entity.MethodNode;
+import io.github.qifan777.knowledge.code.graph.service.CodeGraphService;
+import io.github.qifan777.knowledge.infrastructure.code.CodeAssistantProperties;
+import io.github.qifan777.knowledge.infrastructure.code.JavaParserUtils;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.prompt.PromptTemplate;
+import org.springframework.context.annotation.Description;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Flux;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Description("评审分析给定的java文件")
+@Service
+@AllArgsConstructor
+@Slf4j
+public class AnalyzeFunction implements Function<AnalyzeFunction.Request, String> {
+    private final CodeAssistantProperties properties;
+    private final CodeGraphService codeGraphService;
+    // 防止页面多次调用开启多个线程token消耗过多,demo使用单线程
+    private final Executor executor = Executors.newSingleThreadExecutor();
+    private final ChatModel chatModel;
+
+    public record Request(@JsonProperty(required = true)
+                          @JsonPropertyDescription("java文件路径如:src/main/java/xxx.java") String path) {
+    }
+
+    /**
+     * 重定向到下面的网页进行展示分析结果,由于一个类里面可能有很多个方法,分析的单位是按照方法来分析,因此单独做一个页面来展示分析结果。
+     *
+     * @param request the function argument
+     * @return 重定向链接
+     */
+    @Override
+    public String apply(Request request) {
+        return "请在下面的网页链接查看评审结果:http://localhost:5177/#/analyze?path=" + request.path;
+    }
+
+    /**
+     * 创建一个结果分析流,另起一个线程开启解析java文件,获取主类中的所有方法,然后分析方法调用得到分析结果,推流到前端。
+     *
+     * @param filePath java文件路径 如:io.qifan.github777.UserController.java
+     * @return Flux<AnalyzeResult> 结果分析流
+     */
+    @SneakyThrows
+    public Flux<AnalyzeResult> analyze(String filePath) {
+        log.info("正在评审文件:{}", filePath);
+        return Flux.create(fluxSink -> executor.execute(() -> {
+            JavaParserUtils.parse(Path.of(properties.getProject().getProjectPath(), "src", "main", "java", filePath))
+                    .getResult()
+                    .map(compilationUnit -> compilationUnit.findAll(ClassOrInterfaceDeclaration.class))
+                    // 只分析主类, 可能java文件中一个类都没有,因此返回的是一个Optional。如果不使用FlatMap,会直接返回一个Optional<Optional<ClassOrInterfaceDeclaration>>,
+                    // 因此需要使用flatMap,是的返回结果变成Optional<ClassOrInterfaceDeclaration>
+                    .flatMap(classOrInterfaceDeclarations -> classOrInterfaceDeclarations.stream().findFirst())
+                    // 由于类可能是匿名内部类,这边过滤一下。下面可以直接使用get()。当然实际上这个地方的主类类名肯定是存在的,但是为了写法严谨我还是判断了一下
+                    .filter(classOrInterfaceDeclaration -> classOrInterfaceDeclaration.getFullyQualifiedName().isPresent())
+                    // 获取到类名,并遍历所有方法
+                    .map(classOrInterfaceDeclaration -> {
+                        String fullyQualifiedName = classOrInterfaceDeclaration
+                                .getFullyQualifiedName().get();
+                        return classOrInterfaceDeclaration
+                                .getMethods()
+                                .stream()
+                                .map(methodDeclaration -> fullyQualifiedName + "#" + methodDeclaration.getNameAsString())
+                                .toList();
+                    })
+                    .ifPresentOrElse(methodIds -> {
+                        methodIds.forEach(methodId -> {
+                            analyzeMethod(methodId).doOnNext(fluxSink::next).blockLast();
+                        });
+                    }, () -> {
+                        // 如果没有主类(Class Or Interface),则直接分析整个文件,或者不是Java文件(Mapper.xml)
+                        fluxSink.next(analyzeFile(filePath));
+                    });
+            // 完成
+            fluxSink.complete();
+        }));
+    }
+
+    /**
+     * 分析单个方法调用
+     *
+     * @param methodId 方法ID
+     * @return 分析结果流
+     */
+    public Flux<AnalyzeResult> analyzeMethod(String methodId) {
+        List<MethodNode> childMethods = codeGraphService.findChildMethods(methodId);
+        String prompt = new PromptTemplate("""
+                请你根据{methodId}的调用链评审代码,并给出你的改进建议,并且附带修改后的代码片段,用中文回答。
+                {methodChains}
+                """)
+                .createMessage(Map.of("methodChains", childMethods
+                                .stream()
+                                .map(MethodNode::getContent)
+                                .distinct()
+                                .collect(Collectors.joining("\n")),
+                        "methodId", methodId))
+                .getContent();
+        log.info("评审方法: {}", prompt);
+        String content = childMethods.stream().filter(m -> m.getId().equals(methodId)).findFirst().orElseThrow().getContent();
+        return chatModel.stream(prompt).map(response -> new AnalyzeResult(methodId, response, methodId.split("#")[0], content));
+    }
+
+    public AnalyzeResult analyzeFile(String filePath) {
+        String fileContent = FileUtil.readString(Path.of(properties.getProject().getProjectPath(), filePath).toFile(), StandardCharsets.UTF_8);
+        String prompt = new PromptTemplate("""
+                请你评审一下该提交文件是否有可以改进的地方,并且附带修改后的代码片段,没有请回答无,用中文回答。
+                {content}
+                """)
+                .createMessage(Map.of("content", fileContent)).getContent();
+        log.info("评审文件提示词: {}", prompt);
+        String result = chatModel.call(prompt);
+        log.info("文件分析结果: {}", result);
+        return new AnalyzeResult(filePath, result, filePath, fileContent);
+    }
+
+    @Data
+    @AllArgsConstructor
+    public static class AnalyzeResult {
+        private String id;
+        private String content;
+        private String fileName;
+        private String fileContent;
+    }
+
+
+}

+ 170 - 0
src/main/java/io/github/qifan777/knowledge/code/arthas/ArthasFunction.java

@@ -0,0 +1,170 @@
+package io.github.qifan777.knowledge.code.arthas;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.github.qifan777.knowledge.code.graph.entity.MethodNode;
+import io.github.qifan777.knowledge.code.graph.service.CodeGraphService;
+import io.github.qifan777.knowledge.infrastructure.code.CodeAssistantProperties;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.prompt.PromptTemplate;
+import org.springframework.context.annotation.Description;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+@AllArgsConstructor
+@Description("诊断方法出现异常的原因")
+public class ArthasFunction implements Function<ArthasFunction.Request, String> {
+    private final CodeGraphService codeGraphService;
+    private final CodeAssistantProperties properties;
+    private final ChatModel chatModel;
+
+    public record Request(@JsonProperty(required = true)
+                          @JsonPropertyDescription("类名") String className,
+                          @JsonProperty(required = true)
+                          @JsonPropertyDescription("类名") String methodName) {
+    }
+
+    @Override
+    public String apply(Request request) {
+        String methodId = request.className + "#" + request.methodName;
+        log.info("监听目标:{}", methodId);
+        JobResult jobResult = startWatch(request.className, request.methodName);
+        if (jobResult == null) {
+            return "无异常信息";
+        }
+        String analyzeResult = jobResult
+                .getBody()
+                .getResults()
+                .stream()
+                .filter(r -> r.getType().equals("tt"))
+                .findFirst()
+                .map(result -> {
+                    String methods = codeGraphService.findChildMethods(methodId).stream().map(MethodNode::getContent)
+                            .distinct()
+                            .collect(Collectors.joining("\n"));
+                    TimeFragment timeFragment = result.getTimeFragmentList().get(0);
+                    String content = new PromptTemplate("""
+                            根据下面提供的内容分析异常原因,回答结果用中文
+                            方法名称:{methodName}
+                            方法调用链: {methods}
+                            方法参数:{params}
+                            异常信息:{exp}
+                            """)
+                            .createMessage(Map.of("methodName", timeFragment.getMethodName(),
+                                    "methods", methods,
+                                    "params", timeFragment.getParams().stream().map(param -> param.getObject() == null ? "" : param.getObject().toString()).collect(Collectors.joining("\n")),
+                                    "exp", timeFragment.getThrowExp()))
+                            .getContent();
+                    log.info("代码诊断prompt: {}", content);
+                    return chatModel.call(content);
+                })
+                .orElse("无异常信息");
+        log.info("诊断结果: {}", analyzeResult);
+        return analyzeResult;
+    }
+
+
+    public JobResult startWatch(String className, String method) {
+        CodeAssistantProperties.ArthasProperties arthasProperties = properties.getArthas();
+        RestTemplate restTemplate = new RestTemplate();
+        String encode = Base64.getEncoder().encodeToString((arthasProperties.getUsername() + ":" + arthasProperties.getPassword()).getBytes(StandardCharsets.UTF_8));
+        ArthasRequest arthasRequest = new ArthasRequest().setAction("exec").setCommand("tt -t " + className + " " + method + " -n 1");
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.add("Authorization", "Basic " + encode);
+        HttpEntity<ArthasRequest> requestHttpEntity = new HttpEntity<>(arthasRequest, httpHeaders);
+        return restTemplate.exchange(arthasProperties.getUrl(), HttpMethod.POST, requestHttpEntity, JobResult.class).getBody();
+
+    }
+
+    @Accessors(chain = true)
+    @Data
+    public static class ArthasRequest {
+        private String action;
+        private String command;
+    }
+
+    @Data
+    public static class JobResult {
+        @JsonProperty("body")
+        private Body body;
+        private String message;
+        private String sessionId;
+        private String state;
+    }
+
+    @Data
+    public static class Body {
+        @JsonProperty("command")
+        private String command;
+        private int jobId;
+        private String jobStatus;
+        @JsonProperty("results")
+        private List<Result> results;
+        @JsonProperty("timeExpired")
+        private boolean timeExpired;
+        private int timeout;
+    }
+
+    @Data
+    public static class Result {
+        private Effect effect;
+        private int jobId;
+        private boolean success;
+        private String type;
+        @JsonProperty("timeFragmentList")
+        private List<TimeFragment> timeFragmentList;
+        private int statusCode;
+        private boolean first;
+    }
+
+    @Data
+    public static class Effect {
+        private int classCount;
+        private int cost;
+        private int listenerId;
+        private int methodCount;
+    }
+
+    @Data
+    public static class TimeFragment {
+        private String className;
+        private double cost;
+        private int index;
+        private String methodName;
+        private String object;
+        private List<Param> params;
+        @JsonProperty("return")
+        private boolean isReturn;
+        private String returnObj;
+        @JsonProperty("throw")
+        private boolean isThrow;
+        @JsonProperty("throwExp")
+        private String throwExp;
+        @JsonProperty("timestamp")
+        private String timestamp;
+    }
+
+    @Data
+    public static class Param {
+        private int expand;
+        private JsonNode object;
+    }
+
+}

+ 22 - 0
src/main/java/io/github/qifan777/knowledge/code/graph/controller/CodeGraphController.java

@@ -0,0 +1,22 @@
+package io.github.qifan777.knowledge.code.graph.controller;
+
+import io.github.qifan777.knowledge.code.graph.service.CodeGraphService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("code/graph")
+@AllArgsConstructor
+@Slf4j
+public class CodeGraphController {
+    private final CodeGraphService codeGraphService;
+
+    @PostMapping("build")
+    public String buildGraph() {
+        codeGraphService.buildGraph();
+        return "SUCCESS";
+    }
+}

+ 23 - 0
src/main/java/io/github/qifan777/knowledge/code/graph/entity/ClassNode.java

@@ -0,0 +1,23 @@
+package io.github.qifan777.knowledge.code.graph.entity;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.data.neo4j.core.schema.Id;
+import org.springframework.data.neo4j.core.schema.Node;
+import org.springframework.data.neo4j.core.schema.Relationship;
+
+import java.util.List;
+
+@Node
+@Data
+@Accessors(chain = true)
+public class ClassNode {
+    @Id
+    private String id;
+    private String name;
+    private String content;
+    @Relationship(direction = Relationship.Direction.OUTGOING, type = "OWNS")
+    private List<MethodNode> ownsMethodNodes;
+    @Relationship(direction = Relationship.Direction.OUTGOING, type = "IMPORTS")
+    private List<ClassNode> importNodes;
+}

+ 22 - 0
src/main/java/io/github/qifan777/knowledge/code/graph/entity/MethodNode.java

@@ -0,0 +1,22 @@
+package io.github.qifan777.knowledge.code.graph.entity;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.data.neo4j.core.schema.Id;
+import org.springframework.data.neo4j.core.schema.Node;
+import org.springframework.data.neo4j.core.schema.Relationship;
+
+import java.util.List;
+
+@Node
+@Data
+@Accessors(chain = true)
+public class MethodNode {
+    @Id
+    private String id;
+    private String name;
+    private String comment;
+    private String content;
+    @Relationship(direction = Relationship.Direction.OUTGOING, type = "USES")
+    private List<MethodNode> usesMethodNodes;
+}

+ 7 - 0
src/main/java/io/github/qifan777/knowledge/code/graph/repository/ClassNodeRepository.java

@@ -0,0 +1,7 @@
+package io.github.qifan777.knowledge.code.graph.repository;
+
+import io.github.qifan777.knowledge.code.graph.entity.ClassNode;
+import org.springframework.data.neo4j.repository.Neo4jRepository;
+
+public interface ClassNodeRepository extends Neo4jRepository<ClassNode,String> {
+}

+ 8 - 0
src/main/java/io/github/qifan777/knowledge/code/graph/repository/MethodNodeRepository.java

@@ -0,0 +1,8 @@
+package io.github.qifan777.knowledge.code.graph.repository;
+
+import io.github.qifan777.knowledge.code.graph.entity.MethodNode;
+import org.springframework.data.neo4j.repository.Neo4jRepository;
+
+public interface MethodNodeRepository extends Neo4jRepository<MethodNode, String> {
+
+}

+ 276 - 0
src/main/java/io/github/qifan777/knowledge/code/graph/service/CodeGraphBuilder.java

@@ -0,0 +1,276 @@
+package io.github.qifan777.knowledge.code.graph.service;
+
+import com.github.javaparser.JavaParser;
+import com.github.javaparser.ast.ImportDeclaration;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.comments.Comment;
+import com.github.javaparser.ast.expr.AnnotationExpr;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.MethodCallExpr;
+import io.github.qifan777.knowledge.code.graph.entity.ClassNode;
+import io.github.qifan777.knowledge.code.graph.entity.MethodNode;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.File;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.stream.Stream;
+
+@Slf4j
+public class CodeGraphBuilder {
+    private final Map<String, String> mapperSqlMap = new HashMap<>();
+    private final Map<String, ClassOrInterfaceDeclaration> classDeclarationMap = new HashMap<>();
+    private final Map<String, ClassNode> classNodeMap = new HashMap<>();
+    private final Map<String, MethodNode> methodNodeMap = new HashMap<>();
+    private final Path projectPath;
+    private final JavaParser javaParser;
+
+    public CodeGraphBuilder(String projectPath, JavaParser javaParser) {
+        this.projectPath = Path.of(projectPath);
+        this.javaParser = javaParser;
+    }
+
+    public record BuildContext(Collection<ClassNode> classNodes, Collection<MethodNode> methodNodes) {
+    }
+
+    /**
+     * 构建java方法关系图谱,包含类节点和类方法节点,类和类之间的引用关系,类和方法之间的归属关系,方法和方法之间的调用关系
+     *
+     * @return 构建好的类节点和类方法节点
+     */
+    @SneakyThrows
+    public BuildContext buildGraph() {
+        // 构建mybatis sql
+        buildMapperSqlMap();
+        try (Stream<Path> pathStream = Files.walk(getJavaSourcePath())) {
+            pathStream.filter(path -> path.toFile().isFile())
+                    .flatMap(path -> getClassDeclarations(path).stream())
+                    .filter(declaration -> declaration.getFullyQualifiedName().isPresent())
+                    .forEach(declaration -> classDeclarationMap.put(declaration.getFullyQualifiedName().get(), declaration));
+
+            classDeclarationMap.values().forEach(this::buildClassNode);
+            classNodeMap
+                    .values()
+                    .forEach(classNode -> {
+                        ClassOrInterfaceDeclaration classOrInterfaceDeclaration = classDeclarationMap.get(classNode.getId());
+                        List<MethodDeclaration> methodDeclarations = classOrInterfaceDeclaration.findAll(MethodDeclaration.class);
+                        List<MethodNode> ownsMethodNodes = methodDeclarations
+                                .stream()
+                                .map(methodDeclaration -> buildMethodNode(methodDeclaration.getNameAsString(), classNode.getId(), methodDeclarations))
+                                .filter(Optional::isPresent)
+                                .map(Optional::get)
+                                .toList();
+                        classNode.setOwnsMethodNodes(ownsMethodNodes);
+                    });
+
+        }
+        return new BuildContext(classNodeMap.values(), methodNodeMap.values());
+    }
+
+    /**
+     * 获取指定路径下的java文件中的所有类声明,一个java文件中可能包含多个类
+     *
+     * @param path java文件路径
+     * @return 类声明列表
+     */
+    @SneakyThrows
+    private List<ClassOrInterfaceDeclaration> getClassDeclarations(Path path) {
+        return javaParser.parse(path.toFile())
+                .getResult()
+                .map(compilationUnit -> compilationUnit.findAll(ClassOrInterfaceDeclaration.class))
+                .filter(list -> !list.isEmpty())
+                .orElse(List.of());
+    }
+
+    /**
+     * 递归构建类节点
+     *
+     * @param declaration 类或者接口声明
+     * @return 类节点
+     */
+    @SneakyThrows
+    private Optional<ClassNode> buildClassNode(ClassOrInterfaceDeclaration declaration) {
+        // 用classNodeMap缓存,避免重复构建
+        if (classNodeMap.containsKey(declaration.getNameAsString())) {
+            return Optional.of(classNodeMap.get(declaration.getNameAsString()));
+        }
+        return declaration
+                .getFullyQualifiedName()
+                .map(qualifiedClasName -> {
+                    ClassNode classNode = new ClassNode().setId(qualifiedClasName)
+                            .setName(declaration.getNameAsString())
+                            .setContent(declaration.toString());
+                    // 缓存类节点
+                    classNodeMap.put(qualifiedClasName, classNode);
+                    List<ClassNode> importClassNodes = declaration
+                            .findAll(ImportDeclaration.class)
+                            .stream()
+                            // 递归构建类节点
+                            .map(importDeclaration -> Optional.ofNullable(classDeclarationMap.get(importDeclaration.getNameAsString()))
+                                    .flatMap(this::buildClassNode))
+                            .filter(Optional::isPresent)
+                            .map(Optional::get)
+                            .toList();
+                    classNode.setImportNodes(importClassNodes);
+                    return classNode;
+                });
+    }
+
+    /**
+     * 递归构建方法节点
+     *
+     * @param methodName   方法名称,如login
+     * @param className    方法所在的全限定类名,如io.qifan.xxx.UserService
+     * @param declarations 类中的所有方法,如login、logout等
+     * @return 方法节点
+     */
+    @SneakyThrows
+    private Optional<MethodNode> buildMethodNode(String methodName, String className, List<MethodDeclaration> declarations) {
+        String methodId = className + "#" + methodName;
+        // 用methodNodeMap缓存,避免重复构建
+        if (methodNodeMap.containsKey(methodId)) {
+            return Optional.of(methodNodeMap.get(methodId));
+        }
+        return declarations.stream()
+                .filter(methodDeclaration -> methodDeclaration.getNameAsString().equals(methodName))
+                .findFirst()
+                .map(methodDeclaration -> {
+                    // 获取方法内容,如果是mapper接口则获取方法对应的sql
+                    String content = methodDeclaration.findAll(AnnotationExpr.class)
+                            .stream()
+                            .filter(a -> a.getMetaModel().getQualifiedClassName().equals("org.apache.ibatis.annotations.Mapper"))
+                            .findAny()
+                            .map(annotationExpr -> mapperSqlMap.get(methodId))
+                            .orElse(methodDeclaration.toString());
+                    MethodNode methodNode = new MethodNode()
+                            .setId(methodId)
+                            .setName(methodDeclaration.getNameAsString())
+                            .setContent(content)
+                            .setComment(methodDeclaration.getComment().map(Comment::getContent).orElse(""));
+                    // 缓存方法节点
+                    methodNodeMap.put(methodNode.getId(), methodNode);
+                    // 递归构建方法调用关系
+                    List<MethodNode> usesMethodNodes = methodDeclaration
+                            .findAll(MethodCallExpr.class)
+                            .stream()
+                            .map(this::buildMethodNodeFromMethodCall)
+                            .filter(Optional::isPresent)
+                            .map(Optional::get)
+                            .toList();
+                    methodNode.setUsesMethodNodes(usesMethodNodes);
+                    return methodNode;
+                });
+    }
+
+    /**
+     * userService.login()这段代码指的是methodCall,要获取login()的方法体内容,需要先解析userService中的所有方法,然后取出login方法
+     *
+     * @param methodCallExpr 方法调用表达式
+     * @return 方法节点
+     */
+    public Optional<MethodNode> buildMethodNodeFromMethodCall(MethodCallExpr methodCallExpr) {
+        return methodCallExpr
+                .getScope()
+                .filter(this::checkScopeExist)
+                .flatMap(scope -> buildMethodNode(methodCallExpr.getNameAsString(), scope.calculateResolvedType().asReferenceType().getQualifiedName(), getMethodDeclarationsFromScope(scope)));
+    }
+
+    /**
+     * userService.login(), scope指的是userService. 获取userService中的所有方法
+     *
+     * @param scope 方法所在的对象
+     * @return 方法列表
+     */
+    public List<MethodDeclaration> getMethodDeclarationsFromScope(Expression scope) {
+        return Optional.ofNullable(classDeclarationMap.get(scope.calculateResolvedType()
+                        .asReferenceType().getQualifiedName()))
+                .map(declaration -> declaration.findAll(MethodDeclaration.class))
+                .orElse(List.of());
+    }
+
+    /**
+     * 初始化mybatis mapper xml,提取其中的sql将方法名称和sql语句对应起来
+     */
+    @SneakyThrows
+    private void buildMapperSqlMap() {
+        try (Stream<Path> mapper = Files.walk(getFileInResource("mapper"))) {
+            mapper.filter(path -> path.toString().endsWith(".xml"))
+                    .forEach(file -> {
+                        Document document = parseXMLFileAsDocument(file.toFile());
+                        NodeList selectNodes = document.getDocumentElement().getElementsByTagName("select");
+                        String namespace = document.getDocumentElement().getAttribute("namespace");
+                        extractSqlFromStatement(selectNodes, namespace);
+                        NodeList deleteNodes = document.getDocumentElement().getElementsByTagName("delete");
+                        extractSqlFromStatement(deleteNodes, namespace);
+                        NodeList updateNodes = document.getDocumentElement().getElementsByTagName("update");
+                        extractSqlFromStatement(updateNodes, namespace);
+                    });
+        } catch (Exception ignored) {
+            log.warn("不存在mapper");
+        }
+    }
+
+    /**
+     * 解析mapper xml文件
+     *
+     * @param file mapper文件路径
+     * @return document
+     */
+    @SneakyThrows
+    private Document parseXMLFileAsDocument(File file) {
+        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        return builder.parse(file);
+    }
+
+    /**
+     * 将mapper中的sql语句提取出来,并和sql语句对应的方法名对应起来
+     *
+     * @param nodeList  select/delete/update标签
+     * @param namespace mapper的namespace
+     */
+    @SneakyThrows
+    private void extractSqlFromStatement(NodeList nodeList, String namespace) {
+        TransformerFactory tf = TransformerFactory.newInstance();
+        Transformer transformer = tf.newTransformer();
+        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+        for (int i = 0; i < nodeList.getLength(); i++) {
+            StringWriter writer = new StringWriter();
+            transformer.transform(new DOMSource(nodeList.item(i)), new StreamResult(writer));
+            String output = writer.getBuffer().toString().replaceAll("\n|\r", "");
+            mapperSqlMap.put(namespace + "#" + nodeList.item(i).getAttributes().getNamedItem("id").getNodeValue(), output);
+        }
+    }
+
+
+    private Path getJavaSourcePath() {
+        return projectPath.resolve(Path.of("src", "main", "java"));
+    }
+
+    private Path getFileInResource(String fileName) {
+        return projectPath.resolve(Path.of("src", "main", "resources", fileName));
+    }
+
+
+    private boolean checkScopeExist(Expression expression) {
+        try {
+            expression.calculateResolvedType().asReferenceType();
+        } catch (Exception e) {
+            return false;
+        }
+        return true;
+    }
+
+}

+ 60 - 0
src/main/java/io/github/qifan777/knowledge/code/graph/service/CodeGraphService.java

@@ -0,0 +1,60 @@
+package io.github.qifan777.knowledge.code.graph.service;
+
+import io.github.qifan777.knowledge.code.graph.entity.MethodNode;
+import io.github.qifan777.knowledge.code.graph.repository.ClassNodeRepository;
+import io.github.qifan777.knowledge.code.graph.repository.MethodNodeRepository;
+import io.github.qifan777.knowledge.infrastructure.code.CodeAssistantProperties;
+import io.github.qifan777.knowledge.infrastructure.code.JavaParserUtils;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.neo4j.core.Neo4jClient;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@Service
+@AllArgsConstructor
+public class CodeGraphService {
+    private final ClassNodeRepository classNodeRepository;
+    private final MethodNodeRepository methodNodeRepository;
+    private final Neo4jClient neo4jClient;
+    private final CodeAssistantProperties properties;
+
+    @SneakyThrows
+    public void buildGraph() {
+        methodNodeRepository.deleteAll();
+        classNodeRepository.deleteAll();
+        CodeGraphBuilder.BuildContext buildContext = new CodeGraphBuilder(properties.getProject().getProjectPath(), JavaParserUtils.getJavaParser()).buildGraph();
+        classNodeRepository.saveAll(buildContext.classNodes());
+        log.info("类节点保存完毕: {}", classNodeRepository.count());
+        methodNodeRepository.saveAll(buildContext.methodNodes());
+        log.info("方法节点保存完毕: {}", methodNodeRepository.count());
+    }
+
+    public List<MethodNode> findChildMethods(String methodId) {
+        String cypher = """
+                match window=(m)-[:USES*0..3]->(:MethodNode)
+                where m.id = $methodId
+                with nodes(window) as nodeList
+                unwind nodeList as nodeRows
+                return nodeRows;
+                """;
+        return findMethods(cypher, methodId);
+    }
+
+    public ArrayList<MethodNode> findMethods(String cypher, String methodId) {
+        return new ArrayList<>(neo4jClient.query(cypher)
+                .bind(methodId).to("methodId")
+                .fetchAs(MethodNode.class)
+                .mappedBy((typeSystem, record) -> {
+                    MethodNode methodNode = new MethodNode();
+                    methodNode.setContent(String.valueOf(record.get(0).get("content")));
+                    methodNode.setId(String.valueOf(record.get(0).get("id")).replaceAll("\"", ""));
+                    return methodNode;
+                })
+                .all());
+    }
+}

+ 45 - 0
src/main/java/io/github/qifan777/knowledge/demo/DocumentAnalyzerFunction.java

@@ -0,0 +1,45 @@
+package io.github.qifan777.knowledge.demo;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import lombok.Data;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.reader.tika.TikaDocumentReader;
+import org.springframework.context.annotation.Description;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.stereotype.Service;
+
+import java.util.function.Function;
+
+/**
+ * 通过@Description描述函数的用途,这样ai在多个函数中可以根据描述进行选择。
+ */
+@Description("文档解析函数")
+@Service
+@Slf4j
+public class DocumentAnalyzerFunction implements Function<DocumentAnalyzerFunction.Request, DocumentAnalyzerFunction.Response> {
+    /**
+     * 通过@JsonProperty声明属性名称和是否必填
+     * 通过@JsonPropertyDescription描述属性的用途,这样ai可以提取出符合参数描述的内容。
+     */
+    @Data
+    public static class Request {
+        @JsonProperty(required = true, value = "path")
+        @JsonPropertyDescription(value = "需要解析的本地文件路径")
+        String path;
+    }
+
+    public record Response(String result) {
+    }
+
+    @SneakyThrows
+    @Override
+    public Response apply(Request request) {
+        // ai解析用户的提问得到path参数,使用tika读取本地文件获取内容。把读取到的内容再返回给ai作为上下文去回答用户的问题。
+        TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(new FileSystemResource(request.path));
+        return new Response(tikaDocumentReader.read().get(0).getText());
+    }
+
+
+}

+ 65 - 0
src/main/java/io/github/qifan777/knowledge/demo/DocumentDemoController.java

@@ -0,0 +1,65 @@
+package io.github.qifan777.knowledge.demo;
+
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.reader.tika.TikaDocumentReader;
+import org.springframework.ai.transformer.splitter.TokenTextSplitter;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+@RequestMapping("demo/document")
+@RestController
+@AllArgsConstructor
+@Slf4j
+public class DocumentDemoController {
+    private final VectorStore vectorStore;
+    private final ChatModel chatModel;
+
+    /**
+     * 嵌入文件
+     *
+     * @param file 待嵌入的文件
+     * @return 是否成功
+     */
+    @SneakyThrows
+    @PostMapping("embedding")
+    public Boolean embedding(@RequestParam MultipartFile file) {
+        // 从IO流中读取文件
+        TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(new InputStreamResource(file.getInputStream()));
+        // 将文本内容划分成更小的块
+        List<Document> splitDocuments = new TokenTextSplitter()
+                .apply(tikaDocumentReader.read());
+        // 存入向量数据库,这个过程会自动调用embeddingModel,将文本变成向量再存入。
+        vectorStore.add(splitDocuments);
+        return true;
+    }
+
+    /**
+     * 查询向量数据库
+     *
+     * @param query 用户的提问
+     * @return 匹配到的文档
+     */
+
+    @GetMapping("query")
+    public List<Document> query(@RequestParam String query) {
+        return vectorStore.similaritySearch(query);
+    }
+
+    @GetMapping("chat")
+    public String chat(@RequestParam String query) {
+        return ChatClient.create(chatModel)
+                .prompt(query)
+                .functions("documentAnalyzerFunction")
+                .call()
+                .content();
+    }
+}

+ 160 - 0
src/main/java/io/github/qifan777/knowledge/demo/MessageDemoController.java

@@ -0,0 +1,160 @@
+package io.github.qifan777.knowledge.demo;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.github.qifan777.knowledge.ai.message.dto.AiMessageParams;
+import io.github.qifan777.knowledge.ai.message.dto.AiMessageWrapper;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
+import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor;
+import org.springframework.ai.chat.memory.ChatMemory;
+import org.springframework.ai.chat.memory.InMemoryChatMemory;
+import org.springframework.ai.chat.messages.SystemMessage;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.vectorstore.SearchRequest;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.http.MediaType;
+import org.springframework.http.codec.ServerSentEvent;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Flux;
+
+@RequestMapping("demo/message")
+@RestController
+@AllArgsConstructor
+public class MessageDemoController {
+
+    private final ChatModel chatModel;
+
+
+    private final ObjectMapper objectMapper;
+    private final VectorStore vectorStore;
+    // 模拟数据库存储会话和消息
+    private final ChatMemory chatMemory = new InMemoryChatMemory();
+
+    /**
+     * 非流式问答
+     *
+     * @param prompt 用户提问
+     * @return org.springframework.ai.chat.model.ChatResponse
+     */
+    @GetMapping("chat")
+    public String chat(@RequestParam String prompt) {
+        ChatClient chatClient = ChatClient.create(chatModel);
+        return chatClient.prompt()
+                // 输入单条提示词
+                .user(prompt)
+                // call代表非流式问答,返回的结果可以是ChatResponse,也可以是Entity(转成java类型),也可以是字符串直接提取回答结果。
+                .call()
+                .content();
+    }
+
+    /**
+     * 流式问答
+     *
+     * @param prompt 用户提问
+     * @return SSE流式响应
+     */
+    @GetMapping(value = "chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    public Flux<ServerSentEvent<String>> chatStream(@RequestParam String prompt) {
+        return ChatClient.create(chatModel).prompt()
+                // 输入多条消息,可以将历史消息记录传入
+                .messages(new SystemMessage("你是一个Java智能助手,应用你的Java知识帮助用户解决问题或者编写程序"),
+                        new UserMessage(prompt))
+                // 流式返回
+                .stream()
+                // 构造SSE(ServerSendEvent)格式返回结果
+                .chatResponse().map(chatResponse -> ServerSentEvent.builder(toJson(chatResponse))
+                        .event("message")
+                        .build());
+    }
+
+    /**
+     * 将流式回答结果转json字符串
+     *
+     * @param chatResponse 流式回答结果
+     * @return String json字符串
+     */
+    @SneakyThrows
+    public String toJson(ChatResponse chatResponse) {
+        return objectMapper.writeValueAsString(chatResponse);
+    }
+
+    /**
+     * 调用自定义函数回答用户的提问
+     *
+     * @param prompt       用户的提问
+     * @param functionName 函数名称(bean的名称,类名小写)
+     * @return SSE流式响应
+     */
+    @GetMapping(value = "chat/stream/function", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    public Flux<ServerSentEvent<String>> chatStreamWithFunction(@RequestParam String prompt, @RequestParam String functionName) {
+        return ChatClient.create(chatModel).prompt()
+                .messages(new UserMessage(prompt))
+                // spring ai会从已注册为bean的function中查找函数,将它添加到请求中。如果成功触发就会调用函数
+                .functions(functionName)
+                .stream()
+                .chatResponse()
+                .map(chatResponse -> ServerSentEvent.builder(toJson(chatResponse))
+                        .event("message")
+                        .build());
+    }
+
+    /**
+     * 从向量数据库中查找文档,并将查询的文档作为上下文回答。
+     *
+     * @param prompt 用户的提问
+     * @return SSE流响应
+     */
+    @GetMapping(value = "chat/stream/database", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    public Flux<ServerSentEvent<String>> chatStreamWithDatabase(@RequestParam String prompt) {
+        // question_answer_context是一个占位符,会替换成向量数据库中查询到的文档。QuestionAnswerAdvisor会替换。
+        String promptWithContext = """
+                下面是上下文信息
+                ---------------------
+                {question_answer_context}
+                ---------------------
+                给定的上下文和提供的历史信息,而不是事先的知识,回复用户的意见。如果答案不在上下文中,告诉用户你不能回答这个问题。
+                """;
+        return ChatClient.create(chatModel).prompt()
+                .user(prompt)
+                .advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.builder().build(), promptWithContext))
+                .stream()
+                .content()
+                .map(chatResponse -> ServerSentEvent.builder(chatResponse)
+                        .event("message")
+                        .build());
+    }
+
+    /**
+     * 根据会话id,从数据库中查找历史消息,并将消息作为上下文回答。
+     *
+     * @param prompt    用户的提问
+     * @param sessionId 会话id
+     * @return SSE流响应
+     */
+    @GetMapping(value = "chat/stream/history", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    public Flux<ServerSentEvent<String>> chatStreamWithHistory(@RequestParam String prompt,
+                                                               @RequestParam String sessionId) {
+        // 1. 如果需要存储会话和消息到数据库,自己可以实现ChatMemory接口,这里使用InMemoryChatMemory,内存存储。
+        // 2. 传入会话id,MessageChatMemoryAdvisor会根据会话id去查找消息。
+        // 3. 只需要携带最近10条消息
+        var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, sessionId, 10);
+        return ChatClient.create(chatModel).prompt()
+                .user(prompt)
+                // MessageChatMemoryAdvisor会在消息发送给大模型之前,从ChatMemory中获取会话的历史消息,然后一起发送给大模型。
+                .advisors(messageChatMemoryAdvisor)
+                .stream()
+                .content()
+                .map(chatResponse -> ServerSentEvent.builder(chatResponse)
+                        .event("message")
+                        .build());
+    }
+
+    @PostMapping("ignore")
+    public void ignore(@RequestParam AiMessageWrapper wrapper, @RequestParam AiMessageParams params) {
+    }
+
+}

+ 82 - 0
src/main/java/io/github/qifan777/knowledge/graph/GraphController.java

@@ -0,0 +1,82 @@
+package io.github.qifan777.knowledge.graph;
+
+import io.github.qifan777.knowledge.graph.chunk.ChunkController;
+import io.qifan.infrastructure.common.exception.BusinessException;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.prompt.PromptTemplate;
+import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.data.neo4j.core.Neo4jClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+
+@RestController("graph")
+@AllArgsConstructor
+@Slf4j
+public class GraphController {
+    private final EmbeddingModel embeddingModel;
+    private final ChatModel chatModel;
+    private final Neo4jClient neo4jClient;
+    private final PromptTemplate promptTemplate = new PromptTemplate("""
+            Context information is below.
+            ---------------------
+            {question_answer_context}
+            ---------------------
+            Given the context and provided history information and not prior knowledge,
+            reply to the user comment. If the answer is not in the context, inform
+            the user that you can't answer the question.
+            """);
+
+    @GetMapping(value = "chunk/rag")
+    public String chunkRag(@RequestParam String query) {
+        List<Double> embed = ChunkController.floatsToDoubles(embeddingModel.embed(query));
+        String result = neo4jClient.query("""
+                        CALL db.index.vector.queryNodes('form_10k_chunks', 1, $embedding)
+                        yield node, score
+                        match window=(:Chunk)-[:NEXT*0..1]->(node)-[:NEXT*0..1]->(:Chunk)
+                        with nodes(window) as chunkList, node, score
+                        unwind chunkList as chunkRows
+                        with collect(chunkRows.text) as textList, node, score
+                        return apoc.text.join(textList, " \\n ")
+                        """)
+                .bind(embed).to("embedding")
+                .fetchAs(String.class).first()
+                .orElseThrow(() -> new BusinessException("未找到相似文档"));
+        String content = promptTemplate.createMessage(Map.of("question_answer_context", result)).getContent();
+        return chatModel.call(new UserMessage(content + "\n" + query));
+    }
+
+    @GetMapping(value = "manager/rag")
+    public String managerRag(@RequestParam String query) {
+        List<Double> embed = ChunkController.floatsToDoubles(embeddingModel.embed(query));
+        var result = neo4jClient.query("""
+                        CALL db.index.vector.queryNodes('form_10k_chunks', 1, $embedding)
+                        YIELD node, score
+                        MATCH (node)-[:PART_OF]->(f:Form),
+                            (f)<-[:FILED]-(com:Company),
+                            (com)<-[owns:OWNS_STOCK_IN]-(mgr:Manager)
+                        WITH node, score, mgr, owns, com
+                            ORDER BY owns.shares DESC LIMIT 5
+                        WITH collect (
+                            mgr.name +
+                            " owns " + owns.shares + " of " + com.name +
+                            " at a value of $" + apoc.number.format(owns.value) + "."
+                        ) AS investment_statements, node, score
+                        RETURN "investors: \\n" + apoc.text.join(investment_statements, "\\n") +\s
+                            "\\n" + node.text AS text
+                        """)
+                .bind(embed).to("embedding")
+                .fetchAs(String.class)
+                .first()
+                .orElseThrow(() -> new BusinessException("未找到相似文档"));
+        String content = promptTemplate.createMessage(Map.of("question_answer_context", result)).getContent();
+        log.info("context result: {}", content);
+        return chatModel.call(new UserMessage(content + "\n" + query));
+    }
+}

+ 26 - 0
src/main/java/io/github/qifan777/knowledge/graph/chunk/Chunk.java

@@ -0,0 +1,26 @@
+package io.github.qifan777.knowledge.graph.chunk;
+
+import lombok.Builder;
+import lombok.Data;
+import org.springframework.data.neo4j.core.schema.Id;
+import org.springframework.data.neo4j.core.schema.Node;
+
+import java.util.List;
+
+@Builder
+@Data
+@Node
+public class Chunk {
+    @Id
+    private String id;
+    // 切割后的文本
+    private String text;
+    // item1, item1a, item7, item7a
+    private String item;
+    // Chunk序列号
+    private Integer chunkSeqId;
+    // 属于的Form
+    private String formId;
+    // text的embedding
+    private List<Double> textEmbedding;
+}

+ 133 - 0
src/main/java/io/github/qifan777/knowledge/graph/chunk/ChunkController.java

@@ -0,0 +1,133 @@
+package io.github.qifan777.knowledge.graph.chunk;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.transformer.splitter.TokenTextSplitter;
+import org.springframework.data.neo4j.core.Neo4jClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+@RestController("chunk")
+@AllArgsConstructor
+@Slf4j
+public class ChunkController {
+    private final ChunkRepository chunkRepository;
+    private final Neo4jClient neo4jClient;
+    private final EmbeddingModel embeddingModel;
+
+    /**
+     * 创建Chunk节点,
+     */
+
+    @PostMapping("node")
+    public void createNodes() {
+        var fileDir = new File("F:\\workspace\\code\\learn\\sec-edgar-notebooks\\data\\sample\\form10k");
+        File[] files = fileDir.listFiles();
+        for (File file : files) {
+            if (!file.getName().contains(".json")) continue;
+            chunkRepository.saveAll(fileToChunkList(file));
+        }
+
+    }
+
+    /**
+     * 解析form10k的中的item属性切割成Chunk
+     *
+     * @param file form10k的json文件
+     * @return Chunk节点
+     */
+    @SneakyThrows
+    public List<Chunk> fileToChunkList(File file) {
+        ObjectNode node = new ObjectMapper().readValue(file, ObjectNode.class);
+        // 每个form10k有item1,item1a,item7,item7a四种文本信息,都需要将切割
+        String[] items = {"item1", "item1a", "item7", "item7a"};
+        List<Chunk> chunks = new ArrayList<>();
+        for (String item : items) {
+            String text = node.get(item).asText();
+            // 切割文本成
+            List<Document> documents = new TokenTextSplitter().split(new Document(text));
+            // 最多不超过20 Chunk
+            for (int chunkSeqId = 0; chunkSeqId < Integer.min(documents.size(), 20); chunkSeqId++) {
+                String formId = file.getName().replace(".json", "");
+                chunks.add(Chunk.builder()
+                        .id("%s-%s-chunk%04d".formatted(formId, item, chunkSeqId))
+                        .chunkSeqId(chunkSeqId)
+                        .formId(formId)
+                        .text(documents.get(chunkSeqId).getContent())
+                        .item(item)
+                        .build());
+            }
+        }
+        return chunks;
+    }
+
+    @PostMapping("link")
+    public void createLink() {
+        var formIds = neo4jClient
+                .query("match (c:Chunk) return distinct c.formId as formId")
+                .fetchAs(String.class)
+                .all();
+        // 每个form10k有item1,item1a,item7,item7a四种文本信息,都需要将切割后的Chunk通过NEXT关联起来
+        formIds.forEach(formId -> {
+            List.of("item1", "item1a", "item7", "item7a")
+                    .forEach(item -> {
+                        neo4jClient.query("""
+                                        MATCH (c:Chunk) // 匹配所有的节点
+                                        WHERE c.formId = $formId // 属于同一个form和同一个item的节点
+                                          AND c.item = $item
+                                        WITH c
+                                          ORDER BY c.chunkSeqId ASC // 根据seqId排序一下节点
+                                        WITH collect(c) as section_chunk_list // 转成list
+                                          CALL apoc.nodes.link(section_chunk_list, "NEXT", {avoidDuplicates: true}) // 节点之间依按顺序创建连接
+                                        RETURN size(section_chunk_list)
+                                        """)
+                                .bind(formId).to("formId")
+                                .bind(item).to("item")
+                                .run();
+                    });
+        });
+    }
+
+    /**
+     * 对所有Chunk进行embedding,neo4j中支持向量索引,只有创建索引之后才可以查询相似的向量
+     */
+    @PostMapping("embedding")
+    public void createEmbedding() {
+        // 随便将一段文本转成向量,看看这个嵌入模型的向量维度是多少
+        int dimension = embeddingModel.embed("你好").length;
+
+        // 在Chunk节点创建索引,使用cosine求向量之间的相似度
+        neo4jClient.query("""
+                        CREATE VECTOR INDEX `form_10k_chunks` IF NOT EXISTS
+                        FOR (c:Chunk) ON (c.textEmbedding)
+                        OPTIONS { indexConfig: {
+                        `vector.dimensions`: $dimensions,
+                        `vector.similarity_function`: 'cosine'
+                        }}
+                        """)
+                .bind(dimension).to("dimensions")
+                .run();
+        // 对那些没有嵌入的Chunk进行embedding
+        List<Chunk> waitToEmbedList = chunkRepository.findByTextEmbeddingIsNull();
+        waitToEmbedList.forEach(chunk -> chunk.setTextEmbedding(floatsToDoubles(embeddingModel.embed(chunk.getText()))));
+        chunkRepository.saveAll(waitToEmbedList);
+    }
+
+    public static List<Double> floatsToDoubles(float[] floats) {
+        List<Double> result = new ArrayList<>(floats.length);
+        for (float f : floats) {
+            result.add((double) f);
+        }
+        return result;
+    }
+
+}

+ 9 - 0
src/main/java/io/github/qifan777/knowledge/graph/chunk/ChunkRepository.java

@@ -0,0 +1,9 @@
+package io.github.qifan777.knowledge.graph.chunk;
+
+import org.springframework.data.neo4j.repository.Neo4jRepository;
+
+import java.util.List;
+
+public interface ChunkRepository extends Neo4jRepository<Chunk, String> {
+    List<Chunk> findByTextEmbeddingIsNull();
+}

+ 19 - 0
src/main/java/io/github/qifan777/knowledge/graph/company/Company.java

@@ -0,0 +1,19 @@
+package io.github.qifan777.knowledge.graph.company;
+
+import lombok.Builder;
+import lombok.Data;
+import org.springframework.data.neo4j.core.schema.Id;
+import org.springframework.data.neo4j.core.schema.Node;
+
+import java.util.List;
+
+@Builder
+@Data
+@Node
+public class Company {
+    @Id
+    private String cusip6;
+    private List<String> cusips;
+    private List<String> names;
+    private String name;
+}

+ 51 - 0
src/main/java/io/github/qifan777/knowledge/graph/company/CompanyController.java

@@ -0,0 +1,51 @@
+package io.github.qifan777.knowledge.graph.company;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.github.qifan777.knowledge.graph.model.Form10K;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.neo4j.core.Neo4jClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.File;
+
+@RestController
+@RequestMapping("company")
+@Slf4j
+@AllArgsConstructor
+public class CompanyController {
+    private final CompanyRepository companyRepository;
+    private final Neo4jClient neo4jClient;
+
+    @SneakyThrows
+    @PostMapping("nodes")
+    public void createNodes() {
+        var fileDir = new File("F:\\workspace\\code\\learn\\sec-edgar-notebooks\\data\\sample\\form10k");
+        File[] files = fileDir.listFiles();
+        for (File file : files) {
+            if (!file.getName().contains(".json")) continue;
+            var form10K = new ObjectMapper().readValue(file, Form10K.class);
+            var company = Company.builder().cusip6(form10K.getCusip6())
+                    .cusips(form10K.getCusip())
+                    .names(form10K.getNames())
+                    .name(form10K.getNames().get(0))
+                    .build();
+            companyRepository.save(company);
+        }
+
+    }
+
+    @PostMapping("relationship/filed")
+    public void createFiledRelationship() {
+        // 创建公司和form关系
+        neo4jClient.query("""
+                        MATCH (com:Company), (form:Form)
+                          WHERE com.cusip6 = form.cusip6
+                        MERGE (com)-[:FILED]->(form)
+                        """)
+                .run();
+    }
+}

+ 6 - 0
src/main/java/io/github/qifan777/knowledge/graph/company/CompanyRepository.java

@@ -0,0 +1,6 @@
+package io.github.qifan777.knowledge.graph.company;
+
+import org.springframework.data.neo4j.repository.Neo4jRepository;
+
+public interface CompanyRepository extends Neo4jRepository<Company, String> {
+}

+ 17 - 0
src/main/java/io/github/qifan777/knowledge/graph/form/Form.java

@@ -0,0 +1,17 @@
+package io.github.qifan777.knowledge.graph.form;
+
+import lombok.Builder;
+import lombok.Data;
+import org.springframework.data.neo4j.core.schema.Id;
+import org.springframework.data.neo4j.core.schema.Node;
+
+@Builder
+@Data
+@Node
+public class Form {
+    @Id
+    private String id;
+    private String cusip6;
+    private String source;
+    private String fullText;
+}

+ 70 - 0
src/main/java/io/github/qifan777/knowledge/graph/form/FormController.java

@@ -0,0 +1,70 @@
+package io.github.qifan777.knowledge.graph.form;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.github.qifan777.knowledge.graph.model.Form10K;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.neo4j.core.Neo4jClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.File;
+
+@RestController
+@RequestMapping("form")
+@AllArgsConstructor
+@Slf4j
+public class FormController {
+    private final FormRepository formRepository;
+    private final Neo4jClient neo4jClient;
+
+    @SneakyThrows
+    @PostMapping("node")
+    public void createNodes() {
+        var fileDir = new File("F:\\workspace\\code\\learn\\sec-edgar-notebooks\\data\\sample\\form10k");
+        File[] files = fileDir.listFiles();
+        for (File file : files) {
+            if (!file.getName().contains(".json")) continue;
+            var form10K = new ObjectMapper().readValue(file, Form10K.class);
+            var fullText = "About " +
+                           String.join(",", form10K.getNames()) +
+                           "..." +
+                           form10K.getItem1() +
+                           "\n" +
+                           form10K.getItem1a() +
+                           "\n" +
+                           form10K.getItem7() +
+                           "\n" +
+                           form10K.getItem7a();
+            var formId = file.getName().replace(".json", "");
+            var form = Form.builder().id(formId)
+                    .fullText(fullText)
+                    .cusip6(form10K.getCusip6())
+                    .source(form10K.getSource())
+                    .build();
+            formRepository.save(form);
+        }
+    }
+
+    @PostMapping("relationship/section")
+    public void createSectionRelationship() {
+        neo4jClient.query("""
+                        match (c:Chunk),(f:Form) where c.chunkSeqId=0 and f.id = c.formId
+                        merge (f)-[r:SECTION {item:c.item}] -> (c)
+                        return count(r)
+                        """)
+                .run();
+    }
+
+    @PostMapping("relationship/part-of")
+    public void createPartOfRelationship() {
+        neo4jClient.query("""
+                        match (c:Chunk), (f:Form) where c.formId=f.id
+                        merge (c)-[r:PART_OF]->(f)
+                        return count(r);
+                        """)
+                .run();
+    }
+}

+ 6 - 0
src/main/java/io/github/qifan777/knowledge/graph/form/FormRepository.java

@@ -0,0 +1,6 @@
+package io.github.qifan777.knowledge.graph.form;
+
+import org.springframework.data.neo4j.repository.Neo4jRepository;
+
+public interface FormRepository extends Neo4jRepository<Form, String> {
+}

+ 16 - 0
src/main/java/io/github/qifan777/knowledge/graph/manager/Manager.java

@@ -0,0 +1,16 @@
+package io.github.qifan777.knowledge.graph.manager;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.data.neo4j.core.schema.Id;
+import org.springframework.data.neo4j.core.schema.Node;
+
+@Accessors(chain = true)
+@Data
+@Node
+public class Manager {
+    @Id
+    private String cik;
+    private String name;
+    private String address;
+}

+ 84 - 0
src/main/java/io/github/qifan777/knowledge/graph/manager/ManagerController.java

@@ -0,0 +1,84 @@
+package io.github.qifan777.knowledge.graph.manager;
+
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.event.SyncReadListener;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.github.qifan777.knowledge.graph.model.Form13;
+import io.qifan.infrastructure.common.exception.BusinessException;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.neo4j.core.Neo4jClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("manager")
+@AllArgsConstructor
+@Slf4j
+public class ManagerController {
+
+
+    private final Neo4jClient neo4jClient;
+    private final ObjectMapper jacksonObjectMapper;
+    private final ManagerRepository managerRepository;
+
+    public List<Form13> readForm13List() {
+        SyncReadListener syncReadListener = new SyncReadListener();
+        EasyExcel.read(new File("F:\\workspace\\code\\learn\\sec-edgar-notebooks\\data\\sample\\form13.csv"), Form13.class, syncReadListener)
+                .sheet()
+                .doRead();
+        return syncReadListener.getList()
+                .stream()
+                .map(o -> (Form13) o)
+                .toList();
+    }
+
+    @PostMapping("nodes")
+    public void createNodes() {
+        List<Form13> form13List = readForm13List();
+        // 投资方可能投资了多个证券,所以会有重复的投资方记录,去重一下
+        List<String> cikList = form13List.stream().map(Form13::getManagerCik).distinct().toList();
+        // 每个cik代表一个投资方,映射成Manager对象
+        List<Manager> managerList = cikList.stream().map(cik -> {
+            Form13 manager = form13List.stream()
+                    .filter(form13 -> form13.getManagerCik().equals(cik))
+                    .findFirst()
+                    .orElseThrow(() -> new BusinessException("投资公司不存在"));
+            return new Manager()
+                    .setCik(manager.getManagerCik())
+                    .setName(manager.getManagerName())
+                    .setAddress(manager.getManagerAddress());
+        }).toList();
+        managerRepository.saveAll(managerList);
+    }
+
+    @PostMapping("relationship/stock-in")
+    public void createStockInRelationship() {
+        List<Form13> form13List = readForm13List();
+        form13List.forEach(form13 -> {
+            neo4jClient.query("""
+                            match (m:Manager {cik: $managerCik}), (com:Company {cusip6: $cusip6})
+                            merge (m)-[owns:OWNS_STOCK_IN {reportCalendarOrQuarter: $reportCalendarOrQuarter}]->(com)
+                            on create set
+                                owns.value = $value,
+                                owns.shares = $shares
+                            """)
+                    .bindAll(toMap(form13))
+                    .run();
+        });
+
+    }
+
+    @SneakyThrows
+    public Map<String, Object> toMap(Form13 form13) {
+        return jacksonObjectMapper.readValue(jacksonObjectMapper.writeValueAsString(form13), new TypeReference<>() {
+        });
+    }
+}

+ 6 - 0
src/main/java/io/github/qifan777/knowledge/graph/manager/ManagerRepository.java

@@ -0,0 +1,6 @@
+package io.github.qifan777.knowledge.graph.manager;
+
+import org.springframework.data.neo4j.repository.Neo4jRepository;
+
+public interface ManagerRepository extends Neo4jRepository<Manager, String> {
+}

+ 51 - 0
src/main/java/io/github/qifan777/knowledge/graph/model/Form10K.java

@@ -0,0 +1,51 @@
+package io.github.qifan777.knowledge.graph.model;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * Form 10-K是美国证券交易委员会(SEC)要求上市公司必须每年提交的有关其财务表现与公司运营的综合性报告,
+ * 具体来说包括公司历史,组织架构,财务状况,每股收益,分支机构,高管薪酬等信息。
+ */
+@Data
+public class Form10K {
+    /**
+     * 业务
+     */
+    private String item1;
+    /**
+     * 危险因素
+     */
+    private String item1a;
+    /**
+     * 管理层对财务状况及经营成果的探讨与分析。
+     */
+    private String item7;
+    /**
+     * 市场风险的定量和定性披露
+     */
+    private String item7a;
+    /**
+     * 中央索引键(CIK)用于证券交易委员会的计算机系统,用于识别已向证券交易委员会提交披露文件的公司和个人。
+     */
+    private String cik;
+
+    /**
+     * 。CUSIP的创立是为了给北美的每一个证券一个唯一的代码,这样在清算的时候就不会因为名字相似而出错。
+     * 注意它是为了给每一个证券一个唯一的代码,这个证券包括股票,期权,期货,政府债券,企业债券等所有的证券
+     */
+    private List<String> cusip;
+    /**
+     * CUSIP的前六位是企业的代码
+     */
+    private String cusip6;
+    /**
+     * 公司的名称(包含别名,所以有多个)列表
+     */
+    private List<String> names;
+    /**
+     * 该Form 10-K报告的原文
+     */
+    private String source;
+}

+ 50 - 0
src/main/java/io/github/qifan777/knowledge/graph/model/Form13.java

@@ -0,0 +1,50 @@
+package io.github.qifan777.knowledge.graph.model;
+
+import lombok.Data;
+
+/**
+ * 表单包含投资方公司投资的其他公司、所持股份数量和投资价值的信息。
+ */
+@Data
+public class Form13 {
+    /**
+     * From13表格的原文链接
+     */
+    private String source;
+    /**
+     * 投资方公司的CIK,参考Form10K中的CIK解释
+     */
+    private String managerCik;
+    /**
+     * 投资方公司的名称
+     */
+    private String managerName;
+    /**
+     * 投资方公司的地址
+     */
+    private String managerAddress;
+    /**
+     * Form13报告发布的日期
+     */
+    private String reportCalendarOrQuarter;
+    /**
+     * 参考Form10K中的CUSIP6解释
+     */
+    private String cusip6;
+    /**
+     * 参考Form10K中的CUSIP解释
+     */
+    private String cusip;
+    /**
+     * 被投资公司的名称
+     */
+    private String companyName;
+    /**
+     * 投资的金额
+     */
+    private Double value;
+    /**
+     * 投资份额
+     */
+    private Double shares;
+}

+ 26 - 0
src/main/java/io/github/qifan777/knowledge/infrastructure/code/CodeAssistantProperties.java

@@ -0,0 +1,26 @@
+package io.github.qifan777.knowledge.infrastructure.code;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+
+@ConfigurationProperties(prefix = "code-assistant")
+@Data
+public class CodeAssistantProperties {
+    @NestedConfigurationProperty
+    private ProjectProperties project;
+    @NestedConfigurationProperty
+    private ArthasProperties arthas;
+
+    @Data
+    public static class ProjectProperties {
+        private String projectPath;
+    }
+
+    @Data
+    public static class ArthasProperties {
+        private String url;
+        private String password;
+        private String username;
+    }
+}

+ 40 - 0
src/main/java/io/github/qifan777/knowledge/infrastructure/code/JavaParserUtils.java

@@ -0,0 +1,40 @@
+package io.github.qifan777.knowledge.infrastructure.code;
+
+import com.github.javaparser.JavaParser;
+import com.github.javaparser.ParseResult;
+import com.github.javaparser.ParserConfiguration;
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.symbolsolver.JavaSymbolSolver;
+import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
+import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+import java.nio.file.Path;
+
+@Component
+@Slf4j
+public class JavaParserUtils implements ApplicationContextAware {
+    private static CodeAssistantProperties properties;
+
+    public static JavaParser getJavaParser() {
+        CombinedTypeSolver combinedTypeSolver = new CombinedTypeSolver();
+        combinedTypeSolver.add(new JavaParserTypeSolver(Path.of(properties.getProject().getProjectPath()).resolve(Path.of("src", "main", "java"))));
+        ParserConfiguration parserConfiguration = new ParserConfiguration().setSymbolResolver(new JavaSymbolSolver(combinedTypeSolver));
+        return new JavaParser(parserConfiguration);
+    }
+
+    @SneakyThrows
+    public static ParseResult<CompilationUnit> parse(Path path) {
+        return getJavaParser().parse(path);
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        properties = applicationContext.getBean(CodeAssistantProperties.class);
+    }
+}

+ 89 - 0
src/main/java/io/github/qifan777/knowledge/infrastructure/config/GlobalExceptionAdvice.java

@@ -0,0 +1,89 @@
+package io.github.qifan777.knowledge.infrastructure.config;
+
+import cn.dev33.satoken.exception.DisableServiceException;
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.exception.NotRoleException;
+import io.qifan.infrastructure.common.constants.ResultCode;
+import io.qifan.infrastructure.common.exception.BusinessException;
+import io.qifan.infrastructure.common.exception.SystemException;
+import io.qifan.infrastructure.common.model.R;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.util.ArrayList;
+
+@RestControllerAdvice
+@Slf4j
+public class GlobalExceptionAdvice {
+
+    @ExceptionHandler(BusinessException.class)
+    public ResponseEntity<R<String>> handleBusinessException(BusinessException e) {
+        log.error("业务异常", e);
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+            .body(R.fail(e.getResultCode(), e.getMessage()));
+    }
+
+    @ExceptionHandler(SystemException.class)
+    public ResponseEntity<R<String>> handleSystemException(SystemException e) {
+        log.error("系统异常", e);
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(R.fail(ResultCode.SystemError));
+    }
+
+    @ExceptionHandler(Exception.class)
+    public ResponseEntity<R<String>> handleException(Exception e) {
+        log.error("系统异常", e);
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(R.fail(ResultCode.SystemError));
+    }
+
+    @ExceptionHandler(ConstraintViolationException.class)
+    public ResponseEntity<R<String>> handleValidateException(ConstraintViolationException e) {
+        log.warn("校验异常", e);
+        // 不合格的字段,可能有多个,只需要返回其中一个提示用户就行
+        // 比如密码为空
+        ArrayList<ConstraintViolation<?>> constraintViolations = new ArrayList<>(
+            e.getConstraintViolations());
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+            .body(R.fail(ResultCode.ValidateError,
+                constraintViolations.get(0).getMessage()));
+
+    }
+
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<R<String>> handleValidateExceptionForSpring(
+        MethodArgumentNotValidException e) {
+        log.warn("校验异常", e);
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+            .body(R.fail(ResultCode.ValidateError,
+                e.getBindingResult().getAllErrors().get(0)
+                    .getDefaultMessage()));
+    }
+
+    @ExceptionHandler(NotLoginException.class)
+    public ResponseEntity<R<String>> handleNotLogin(NotLoginException e) {
+        log.error("未登录", e);
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+            .body(R.fail(ResultCode.Unauthorized));
+    }
+
+    @ExceptionHandler(NotRoleException.class)
+    public ResponseEntity<R<String>> handleNotRole(NotRoleException e) {
+        log.error("角色校验异常", e);
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+            .body(R.fail(ResultCode.NotGrant, e.getMessage()));
+    }
+
+    @ExceptionHandler(DisableServiceException.class)
+    public ResponseEntity<R<String>> handleDisabledException(DisableServiceException e) {
+        log.error("账号封禁", e);
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+            .body(R.fail(ResultCode.StatusHasInvalid, "账号已被封禁"));
+    }
+
+
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно