diff --git a/frontend/.editorconfig b/frontend/.editorconfig
index 3ba168a59..54955d935 100644
--- a/frontend/.editorconfig
+++ b/frontend/.editorconfig
@@ -1,13 +1,13 @@
-root = true
-
-[*]
-charset = utf-8
-end_of_line = lf
-indent_style = space
-indent_size = 2
-insert_final_newline = true
-trim_trailing_whitespace = true
-
-[*.{md}]
-trim_trailing_whitespace = false
-
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{md}]
+trim_trailing_whitespace = false
+
diff --git a/frontend/.gitignore b/frontend/.gitignore
index 7a14a7a70..2e8f378eb 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -1,28 +1,28 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-lerna-debug.log*
-
-node_modules
-dist
-dist-ssr
-*.local
-
-src/mock/sessions/*
-
-# Editor directories and files
-.vscode/*
-!.vscode/extensions.json
-.idea
-.DS_Store
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?
-
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+src/mock/sessions/*
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
.vite
\ No newline at end of file
diff --git a/frontend/README.md b/frontend/README.md
index 8bea5e6fc..b4fcd04d0 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -1,96 +1,96 @@
-🚀 快速开始
-
-```
-npm install # 安装依赖
-npm run dev # 启动项目
-npm run mock # 启动后台Mock服务(可选)
-```
-
-📁 项目结构
-
-```
-frontend/
-├── public/ # 📖 文档中心
-│ ├── huawei-logo.webp/ # logo
-│ └── xxx/ # 标注工作台(可分离部署)
-│
-├── src/ # 🎨 前端应用
-│ ├── apps/ # 多前端应用
-│ │ ├── console/ # 数据工作台&运营控制台
-│ │ │ ├── next.config.js
-│ │ │ ├── package.json
-│ │ │ └── src/
-│ │ └── annotation-studio/ # 标注工作台(可分离部署)
-│ │
-│ ├── assets/ # 共享UI组件/SDK
-│ │ ├── xxx/ # 数据工作台&运营控制台
-│ │ │ ├── next.config.js
-│ │ │ └── src/
-│ │ │
-│ │ │
-│ │ └── xxx/ # 数据工作台&运营控制台
-│ │ ├── package.json
-│ │ └── src/
-│ │
-│ ├── components/ # 构建与环境配置
-│ │ ├── CardView.tsx # 数据工作台&运营控制台
-│ │ ├── DetailHeader.tsx # 数据工作台&运营控制台
-│ │ ├── RadioCard.tsx # 数据工作台&运营控制台
-│ │ ├── SearchControls # 数据工作台&运营控制台
-│ │ ├── TagList # 标注工作台(可分离部署)
-│ │ └── TaskPopover # 标注工作台(可分离部署)
-│ │
-│ ├── hooks/ # 构建与环境配置
-│ │ ├── console/ # 数据工作台&运营控制台
-│ │ ├── next.config.js
-│ │ ├── next.config.js
-│ │ ├── next.config.js
-│ │ ├── next.config.js
-│ │ ├── next.config.js
-│ │ └── annotation-studio/ # 标注工作台(可分离部署)
-│ │
-│ ├── mock/ # 构建与环境配置
-│ │ ├── console/ # 数据工作台&运营控制台
-│ │ ├── next.config.js
-│ │ ├── next.config.js
-│ │ ├── next.config.js
-│ │ ├── next.config.js
-│ │ └── annotation-studio/ # 标注工作台(可分离部署)
-│ │
-│ ├── pages/ # 构建与环境配置
-│ │ ├── console/ # 数据工作台&运营控制台
-│ │ │ ├── next.config.js
-│ │ │ ├── package.json
-│ │ │ └── src/
-│ │ └── annotation-studio/ # 标注工作台(可分离部署)
-│ │
-│ ├── providers/ # 构建与环境配置
-│ │ ├── console/ # 数据工作台&运营控制台
-│ │ │ ├── next.config.js
-│ │ │ ├── package.json
-│ │ │ └── src/
-│ │ └── annotation-studio/ # 标注工作台(可分离部署)
-│ │
-│ ├── routes/ # 构建与环境配置
-│ │ └── next.config.js
-│ │
-│ ├── types/ # 构建与环境配置
-│ │ ├── next.config.js
-│ │ ├── next.config.js
-│ │ ├── next.config.js
-│ │ ├── next.config.js
-│ │ └── next.config.js
-│ │
-│ └── utils/ # 构建与环境配置
-│ ├── next.config.js
-│ ├── next.config.js
-│ └── next.config.js
-│
-├── eslint.config.js/ # 🔧 后端服务架构
-├── index.html/ # 🔧 后端服务架构
-├── package.json/ # 🔧 后端服务架构
-├── README.md # 项目说明
-├── tailwind.config.ts # 更新日志
-├── vite.config.ts # 开源协议
-└── pom.xml # Maven根配置
-```
+🚀 快速开始
+
+```
+npm install # 安装依赖
+npm run dev # 启动项目
+npm run mock # 启动后台Mock服务(可选)
+```
+
+📁 项目结构
+
+```
+frontend/
+├── public/ # 📖 文档中心
+│ ├── huawei-logo.webp/ # logo
+│ └── xxx/ # 标注工作台(可分离部署)
+│
+├── src/ # 🎨 前端应用
+│ ├── apps/ # 多前端应用
+│ │ ├── console/ # 数据工作台&运营控制台
+│ │ │ ├── next.config.js
+│ │ │ ├── package.json
+│ │ │ └── src/
+│ │ └── annotation-studio/ # 标注工作台(可分离部署)
+│ │
+│ ├── assets/ # 共享UI组件/SDK
+│ │ ├── xxx/ # 数据工作台&运营控制台
+│ │ │ ├── next.config.js
+│ │ │ └── src/
+│ │ │
+│ │ │
+│ │ └── xxx/ # 数据工作台&运营控制台
+│ │ ├── package.json
+│ │ └── src/
+│ │
+│ ├── components/ # 构建与环境配置
+│ │ ├── CardView.tsx # 数据工作台&运营控制台
+│ │ ├── DetailHeader.tsx # 数据工作台&运营控制台
+│ │ ├── RadioCard.tsx # 数据工作台&运营控制台
+│ │ ├── SearchControls # 数据工作台&运营控制台
+│ │ ├── TagList # 标注工作台(可分离部署)
+│ │ └── TaskPopover # 标注工作台(可分离部署)
+│ │
+│ ├── hooks/ # 构建与环境配置
+│ │ ├── console/ # 数据工作台&运营控制台
+│ │ ├── next.config.js
+│ │ ├── next.config.js
+│ │ ├── next.config.js
+│ │ ├── next.config.js
+│ │ ├── next.config.js
+│ │ └── annotation-studio/ # 标注工作台(可分离部署)
+│ │
+│ ├── mock/ # 构建与环境配置
+│ │ ├── console/ # 数据工作台&运营控制台
+│ │ ├── next.config.js
+│ │ ├── next.config.js
+│ │ ├── next.config.js
+│ │ ├── next.config.js
+│ │ └── annotation-studio/ # 标注工作台(可分离部署)
+│ │
+│ ├── pages/ # 构建与环境配置
+│ │ ├── console/ # 数据工作台&运营控制台
+│ │ │ ├── next.config.js
+│ │ │ ├── package.json
+│ │ │ └── src/
+│ │ └── annotation-studio/ # 标注工作台(可分离部署)
+│ │
+│ ├── providers/ # 构建与环境配置
+│ │ ├── console/ # 数据工作台&运营控制台
+│ │ │ ├── next.config.js
+│ │ │ ├── package.json
+│ │ │ └── src/
+│ │ └── annotation-studio/ # 标注工作台(可分离部署)
+│ │
+│ ├── routes/ # 构建与环境配置
+│ │ └── next.config.js
+│ │
+│ ├── types/ # 构建与环境配置
+│ │ ├── next.config.js
+│ │ ├── next.config.js
+│ │ ├── next.config.js
+│ │ ├── next.config.js
+│ │ └── next.config.js
+│ │
+│ └── utils/ # 构建与环境配置
+│ ├── next.config.js
+│ ├── next.config.js
+│ └── next.config.js
+│
+├── eslint.config.js/ # 🔧 后端服务架构
+├── index.html/ # 🔧 后端服务架构
+├── package.json/ # 🔧 后端服务架构
+├── README.md # 项目说明
+├── tailwind.config.ts # 更新日志
+├── vite.config.ts # 开源协议
+└── pom.xml # Maven根配置
+```
diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js
index 63d950bd5..d94e7deb7 100644
--- a/frontend/eslint.config.js
+++ b/frontend/eslint.config.js
@@ -1,23 +1,23 @@
-import js from '@eslint/js'
-import globals from 'globals'
-import reactHooks from 'eslint-plugin-react-hooks'
-import reactRefresh from 'eslint-plugin-react-refresh'
-import tseslint from 'typescript-eslint'
-import { globalIgnores } from 'eslint/config'
-
-export default tseslint.config([
- globalIgnores(['dist']),
- {
- files: ['**/*.{ts,tsx}'],
- extends: [
- js.configs.recommended,
- tseslint.configs.recommended,
- reactHooks.configs['recommended-latest'],
- reactRefresh.configs.vite,
- ],
- languageOptions: {
- ecmaVersion: 2020,
- globals: globals.browser,
- },
- },
-])
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { globalIgnores } from 'eslint/config'
+
+export default tseslint.config([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs['recommended-latest'],
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ },
+])
diff --git a/frontend/index.html b/frontend/index.html
index 5337f9cfb..278d49b30 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1,13 +1,13 @@
-
-
-
-
-
-
- DataMate
-
-
-
-
-
-
+
+
+
+
+
+
+ DataMate
+
+
+
+
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 3b341b98b..f48ffc742 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1,7225 +1,7225 @@
-{
- "name": "edatamate",
- "version": "0.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "edatamate",
- "version": "0.0.0",
- "dependencies": {
- "@reduxjs/toolkit": "^2.11.0",
- "@xyflow/react": "^12.8.3",
- "antd": "^5.27.0",
- "jssha": "^3.3.1",
- "lucide-react": "^0.539.0",
- "react": "^18.1.1",
- "react-dom": "^18.1.1",
- "react-redux": "^9.2.0",
- "react-router": "^7.8.0",
- "recharts": "2.15.0"
- },
- "devDependencies": {
- "@eslint/js": "^9.33.0",
- "@tailwindcss/vite": "^4.1.12",
- "@types/node": "^24.2.1",
- "@types/react": "^18.1.10",
- "@types/react-dom": "^18.1.7",
- "@vitejs/plugin-react": "^5.0.0",
- "body-parser": "^2.2.0",
- "eslint": "^9.33.0",
- "eslint-plugin-react-hooks": "^5.2.0",
- "eslint-plugin-react-refresh": "^0.4.20",
- "express": "^5.1.0",
- "express-session": "^1.18.2",
- "fs-extra": "^11.3.1",
- "globals": "^16.3.0",
- "lodash": "^4.17.21",
- "minimist": "^1.2.8",
- "mockjs": "^1.1.0",
- "nodemon": "^3.1.10",
- "postcss": "^8.5.6",
- "session-file-store": "^1.5.0",
- "tailwindcss": "^4.1.12",
- "typescript": "~5.8.3",
- "typescript-eslint": "^8.39.1",
- "vite": "^7.1.2"
- }
- },
- "node_modules/@ampproject/remapping": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@ant-design/colors": {
- "version": "7.2.1",
- "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz",
- "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==",
- "license": "MIT",
- "dependencies": {
- "@ant-design/fast-color": "^2.0.6"
- }
- },
- "node_modules/@ant-design/cssinjs": {
- "version": "1.24.0",
- "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.24.0.tgz",
- "integrity": "sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.11.1",
- "@emotion/hash": "^0.8.0",
- "@emotion/unitless": "^0.7.5",
- "classnames": "^2.3.1",
- "csstype": "^3.1.3",
- "rc-util": "^5.35.0",
- "stylis": "^4.3.4"
- },
- "peerDependencies": {
- "react": ">=16.0.0",
- "react-dom": ">=16.0.0"
- }
- },
- "node_modules/@ant-design/cssinjs-utils": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz",
- "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==",
- "license": "MIT",
- "dependencies": {
- "@ant-design/cssinjs": "^1.21.0",
- "@babel/runtime": "^7.23.2",
- "rc-util": "^5.38.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@ant-design/fast-color": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz",
- "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.24.7"
- },
- "engines": {
- "node": ">=8.x"
- }
- },
- "node_modules/@ant-design/icons": {
- "version": "5.6.1",
- "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz",
- "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==",
- "license": "MIT",
- "dependencies": {
- "@ant-design/colors": "^7.0.0",
- "@ant-design/icons-svg": "^4.4.0",
- "@babel/runtime": "^7.24.8",
- "classnames": "^2.2.6",
- "rc-util": "^5.31.1"
- },
- "engines": {
- "node": ">=8"
- },
- "peerDependencies": {
- "react": ">=16.0.0",
- "react-dom": ">=16.0.0"
- }
- },
- "node_modules/@ant-design/icons-svg": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
- "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==",
- "license": "MIT"
- },
- "node_modules/@ant-design/react-slick": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz",
- "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.4",
- "classnames": "^2.2.5",
- "json2mq": "^0.2.0",
- "resize-observer-polyfill": "^1.5.1",
- "throttle-debounce": "^5.0.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0"
- }
- },
- "node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/compat-data": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
- "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
- "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.0",
- "@babel/helper-compilation-targets": "^7.27.2",
- "@babel/helper-module-transforms": "^7.27.3",
- "@babel/helpers": "^7.27.6",
- "@babel/parser": "^7.28.0",
- "@babel/template": "^7.27.2",
- "@babel/traverse": "^7.28.0",
- "@babel/types": "^7.28.0",
- "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"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
- "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.28.0",
- "@babel/types": "^7.28.0",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
- "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.27.2",
- "@babel/helper-validator-option": "^7.27.1",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-globals": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
- "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
- "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.27.1",
- "@babel/types": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.27.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
- "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.27.3"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-plugin-utils": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
- "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
- "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-option": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.28.2",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz",
- "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
- "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.0"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-self": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
- "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-source": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
- "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/runtime": {
- "version": "7.28.2",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz",
- "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/template": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
- "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/parser": "^7.27.2",
- "@babel/types": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
- "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.0",
- "@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.28.0",
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.0",
- "debug": "^4.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.28.2",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
- "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@emotion/hash": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
- "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
- "license": "MIT"
- },
- "node_modules/@emotion/unitless": {
- "version": "0.7.5",
- "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
- "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
- "license": "MIT"
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz",
- "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz",
- "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz",
- "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz",
- "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz",
- "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz",
- "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz",
- "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz",
- "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz",
- "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz",
- "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz",
- "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz",
- "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz",
- "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz",
- "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz",
- "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz",
- "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz",
- "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz",
- "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz",
- "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz",
- "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz",
- "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openharmony-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz",
- "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz",
- "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz",
- "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz",
- "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz",
- "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
- "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
- }
- },
- "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
- "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
- }
- },
- "node_modules/@eslint/config-array": {
- "version": "0.21.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
- "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/object-schema": "^2.1.6",
- "debug": "^4.3.1",
- "minimatch": "^3.1.2"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/config-helpers": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
- "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/core": {
- "version": "0.15.2",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
- "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/json-schema": "^7.0.15"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/eslintrc": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
- "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.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": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint/eslintrc/node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@eslint/js": {
- "version": "9.33.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz",
- "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- }
- },
- "node_modules/@eslint/object-schema": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
- "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/plugin-kit": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
- "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.15.2",
- "levn": "^0.4.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node": {
- "version": "0.16.6",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
- "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.3.0"
- },
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
- "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/retry": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
- "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@isaacs/fs-minipass": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
- "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "minipass": "^7.0.4"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.13",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/remapping": {
- "version": "2.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
- "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.30",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
- "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
- "license": "MIT",
- "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.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@rc-component/async-validator": {
- "version": "5.0.4",
- "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz",
- "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.24.4"
- },
- "engines": {
- "node": ">=14.x"
- }
- },
- "node_modules/@rc-component/color-picker": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz",
- "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==",
- "license": "MIT",
- "dependencies": {
- "@ant-design/fast-color": "^2.0.6",
- "@babel/runtime": "^7.23.6",
- "classnames": "^2.2.6",
- "rc-util": "^5.38.1"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@rc-component/context": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz",
- "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "rc-util": "^5.27.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@rc-component/mini-decimal": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz",
- "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.18.0"
- },
- "engines": {
- "node": ">=8.x"
- }
- },
- "node_modules/@rc-component/mutate-observer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz",
- "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.18.0",
- "classnames": "^2.3.2",
- "rc-util": "^5.24.4"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@rc-component/portal": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz",
- "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.18.0",
- "classnames": "^2.3.2",
- "rc-util": "^5.24.4"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@rc-component/qrcode": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.0.0.tgz",
- "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.24.7",
- "classnames": "^2.3.2",
- "rc-util": "^5.38.0"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@rc-component/tour": {
- "version": "1.15.1",
- "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz",
- "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.18.0",
- "@rc-component/portal": "^1.0.0-9",
- "@rc-component/trigger": "^2.0.0",
- "classnames": "^2.3.2",
- "rc-util": "^5.24.4"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@rc-component/trigger": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.3.0.tgz",
- "integrity": "sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.23.2",
- "@rc-component/portal": "^1.1.0",
- "classnames": "^2.3.2",
- "rc-motion": "^2.0.0",
- "rc-resize-observer": "^1.3.1",
- "rc-util": "^5.44.0"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/@reduxjs/toolkit": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.0.tgz",
- "integrity": "sha512-hBjYg0aaRL1O2Z0IqWhnTLytnjDIxekmRxm1snsHjHaKVmIF1HiImWqsq+PuEbn6zdMlkIj9WofK1vR8jjx+Xw==",
- "license": "MIT",
- "dependencies": {
- "@standard-schema/spec": "^1.0.0",
- "@standard-schema/utils": "^0.3.0",
- "immer": "^11.0.0",
- "redux": "^5.0.1",
- "redux-thunk": "^3.1.0",
- "reselect": "^5.1.0"
- },
- "peerDependencies": {
- "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
- "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "react": {
- "optional": true
- },
- "react-redux": {
- "optional": true
- }
- }
- },
- "node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-beta.30",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.30.tgz",
- "integrity": "sha512-whXaSoNUFiyDAjkUF8OBpOm77Szdbk5lGNqFe6CbVbJFrhCCPinCbRA3NjawwlNHla1No7xvXXh+CpSxnPfUEw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz",
- "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz",
- "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz",
- "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz",
- "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz",
- "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz",
- "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz",
- "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz",
- "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz",
- "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz",
- "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz",
- "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz",
- "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz",
- "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz",
- "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz",
- "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz",
- "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz",
- "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz",
- "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz",
- "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz",
- "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@standard-schema/spec": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
- "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
- "license": "MIT"
- },
- "node_modules/@standard-schema/utils": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
- "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
- "license": "MIT"
- },
- "node_modules/@tailwindcss/node": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.12.tgz",
- "integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/remapping": "^2.3.4",
- "enhanced-resolve": "^5.18.3",
- "jiti": "^2.5.1",
- "lightningcss": "1.30.1",
- "magic-string": "^0.30.17",
- "source-map-js": "^1.2.1",
- "tailwindcss": "4.1.12"
- }
- },
- "node_modules/@tailwindcss/oxide": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.12.tgz",
- "integrity": "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "dependencies": {
- "detect-libc": "^2.0.4",
- "tar": "^7.4.3"
- },
- "engines": {
- "node": ">= 10"
- },
- "optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.1.12",
- "@tailwindcss/oxide-darwin-arm64": "4.1.12",
- "@tailwindcss/oxide-darwin-x64": "4.1.12",
- "@tailwindcss/oxide-freebsd-x64": "4.1.12",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12",
- "@tailwindcss/oxide-linux-arm64-musl": "4.1.12",
- "@tailwindcss/oxide-linux-x64-gnu": "4.1.12",
- "@tailwindcss/oxide-linux-x64-musl": "4.1.12",
- "@tailwindcss/oxide-wasm32-wasi": "4.1.12",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12",
- "@tailwindcss/oxide-win32-x64-msvc": "4.1.12"
- }
- },
- "node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz",
- "integrity": "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz",
- "integrity": "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz",
- "integrity": "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz",
- "integrity": "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz",
- "integrity": "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz",
- "integrity": "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz",
- "integrity": "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.12.tgz",
- "integrity": "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.12.tgz",
- "integrity": "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-wasm32-wasi": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz",
- "integrity": "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==",
- "bundleDependencies": [
- "@napi-rs/wasm-runtime",
- "@emnapi/core",
- "@emnapi/runtime",
- "@tybys/wasm-util",
- "@emnapi/wasi-threads",
- "tslib"
- ],
- "cpu": [
- "wasm32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/core": "^1.4.5",
- "@emnapi/runtime": "^1.4.5",
- "@emnapi/wasi-threads": "^1.0.4",
- "@napi-rs/wasm-runtime": "^0.2.12",
- "@tybys/wasm-util": "^0.10.0",
- "tslib": "^2.8.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz",
- "integrity": "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz",
- "integrity": "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/vite": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.12.tgz",
- "integrity": "sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@tailwindcss/node": "4.1.12",
- "@tailwindcss/oxide": "4.1.12",
- "tailwindcss": "4.1.12"
- },
- "peerDependencies": {
- "vite": "^5.2.0 || ^6 || ^7"
- }
- },
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
- }
- },
- "node_modules/@types/babel__generator": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
- "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__traverse": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
- "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.2"
- }
- },
- "node_modules/@types/d3-array": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
- "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
- "license": "MIT"
- },
- "node_modules/@types/d3-color": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
- "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
- "license": "MIT"
- },
- "node_modules/@types/d3-drag": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
- "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
- "license": "MIT",
- "dependencies": {
- "@types/d3-selection": "*"
- }
- },
- "node_modules/@types/d3-ease": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
- "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
- "license": "MIT"
- },
- "node_modules/@types/d3-interpolate": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
- "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
- "license": "MIT",
- "dependencies": {
- "@types/d3-color": "*"
- }
- },
- "node_modules/@types/d3-path": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
- "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
- "license": "MIT"
- },
- "node_modules/@types/d3-scale": {
- "version": "4.0.9",
- "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
- "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
- "license": "MIT",
- "dependencies": {
- "@types/d3-time": "*"
- }
- },
- "node_modules/@types/d3-selection": {
- "version": "3.0.11",
- "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
- "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
- "license": "MIT"
- },
- "node_modules/@types/d3-shape": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
- "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
- "license": "MIT",
- "dependencies": {
- "@types/d3-path": "*"
- }
- },
- "node_modules/@types/d3-time": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
- "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
- "license": "MIT"
- },
- "node_modules/@types/d3-timer": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
- "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
- "license": "MIT"
- },
- "node_modules/@types/d3-transition": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
- "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
- "license": "MIT",
- "dependencies": {
- "@types/d3-selection": "*"
- }
- },
- "node_modules/@types/d3-zoom": {
- "version": "3.0.8",
- "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
- "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
- "license": "MIT",
- "dependencies": {
- "@types/d3-interpolate": "*",
- "@types/d3-selection": "*"
- }
- },
- "node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "24.2.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz",
- "integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~7.10.0"
- }
- },
- "node_modules/@types/prop-types": {
- "version": "15.7.15",
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
- "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/@types/react": {
- "version": "18.3.23",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
- "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "@types/prop-types": "*",
- "csstype": "^3.0.2"
- }
- },
- "node_modules/@types/react-dom": {
- "version": "18.3.7",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
- "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "^18.0.0"
- }
- },
- "node_modules/@types/use-sync-external-store": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
- "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
- "license": "MIT"
- },
- "node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.39.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz",
- "integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.39.1",
- "@typescript-eslint/type-utils": "8.39.1",
- "@typescript-eslint/utils": "8.39.1",
- "@typescript-eslint/visitor-keys": "8.39.1",
- "graphemer": "^1.4.0",
- "ignore": "^7.0.0",
- "natural-compare": "^1.4.0",
- "ts-api-utils": "^2.1.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "@typescript-eslint/parser": "^8.39.1",
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
- "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/@typescript-eslint/parser": {
- "version": "8.39.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.1.tgz",
- "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/scope-manager": "8.39.1",
- "@typescript-eslint/types": "8.39.1",
- "@typescript-eslint/typescript-estree": "8.39.1",
- "@typescript-eslint/visitor-keys": "8.39.1",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/project-service": {
- "version": "8.39.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.1.tgz",
- "integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.39.1",
- "@typescript-eslint/types": "^8.39.1",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "8.39.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz",
- "integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.39.1",
- "@typescript-eslint/visitor-keys": "8.39.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.39.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz",
- "integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/type-utils": {
- "version": "8.39.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz",
- "integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.39.1",
- "@typescript-eslint/typescript-estree": "8.39.1",
- "@typescript-eslint/utils": "8.39.1",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.1.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/types": {
- "version": "8.39.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz",
- "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.39.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz",
- "integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/project-service": "8.39.1",
- "@typescript-eslint/tsconfig-utils": "8.39.1",
- "@typescript-eslint/types": "8.39.1",
- "@typescript-eslint/visitor-keys": "8.39.1",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.1.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@typescript-eslint/utils": {
- "version": "8.39.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.1.tgz",
- "integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.39.1",
- "@typescript-eslint/types": "8.39.1",
- "@typescript-eslint/typescript-estree": "8.39.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.39.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz",
- "integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.39.1",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@vitejs/plugin-react": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.0.tgz",
- "integrity": "sha512-Jx9JfsTa05bYkS9xo0hkofp2dCmp1blrKjw9JONs5BTHOvJCgLbaPSuZLGSVJW6u2qe0tc4eevY0+gSNNi0YCw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.28.0",
- "@babel/plugin-transform-react-jsx-self": "^7.27.1",
- "@babel/plugin-transform-react-jsx-source": "^7.27.1",
- "@rolldown/pluginutils": "1.0.0-beta.30",
- "@types/babel__core": "^7.20.5",
- "react-refresh": "^0.17.0"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
- }
- },
- "node_modules/@xyflow/react": {
- "version": "12.8.3",
- "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.3.tgz",
- "integrity": "sha512-8sdRZPMCzfhauF96krlUMPCKmi9cX64HsYG8qoVAAvTKDAqxXg7RSp/IhoXlzbI/lsRD1vAxeDBxvI/XqACa6g==",
- "license": "MIT",
- "dependencies": {
- "@xyflow/system": "0.0.67",
- "classcat": "^5.0.3",
- "zustand": "^4.4.0"
- },
- "peerDependencies": {
- "react": ">=17",
- "react-dom": ">=17"
- }
- },
- "node_modules/@xyflow/system": {
- "version": "0.0.67",
- "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.67.tgz",
- "integrity": "sha512-hYsmbj+8JDei0jmupBmxNLaeJEcf9kKmMl6IziGe02i0TOCsHwjIdP+qz+f4rI1/FR2CQiCZJrw4dkHOLC6tEQ==",
- "license": "MIT",
- "dependencies": {
- "@types/d3-drag": "^3.0.7",
- "@types/d3-interpolate": "^3.0.4",
- "@types/d3-selection": "^3.0.10",
- "@types/d3-transition": "^3.0.8",
- "@types/d3-zoom": "^3.0.8",
- "d3-drag": "^3.0.0",
- "d3-interpolate": "^3.0.1",
- "d3-selection": "^3.0.0",
- "d3-zoom": "^3.0.0"
- }
- },
- "node_modules/accepts": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
- "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mime-types": "^3.0.0",
- "negotiator": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/acorn": {
- "version": "8.15.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "license": "MIT",
- "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"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/antd": {
- "version": "5.27.0",
- "resolved": "https://registry.npmjs.org/antd/-/antd-5.27.0.tgz",
- "integrity": "sha512-o54dmpooLOc08RSGCkeEQBYAGPxUSmnhmYJKCNTHH46vzjOVxdteu+wPTRVkRbAkDTbs2VcNr5VL7Lu67rPIiA==",
- "license": "MIT",
- "dependencies": {
- "@ant-design/colors": "^7.2.1",
- "@ant-design/cssinjs": "^1.23.0",
- "@ant-design/cssinjs-utils": "^1.1.3",
- "@ant-design/fast-color": "^2.0.6",
- "@ant-design/icons": "^5.6.1",
- "@ant-design/react-slick": "~1.1.2",
- "@babel/runtime": "^7.26.0",
- "@rc-component/color-picker": "~2.0.1",
- "@rc-component/mutate-observer": "^1.1.0",
- "@rc-component/qrcode": "~1.0.0",
- "@rc-component/tour": "~1.15.1",
- "@rc-component/trigger": "^2.3.0",
- "classnames": "^2.5.1",
- "copy-to-clipboard": "^3.3.3",
- "dayjs": "^1.11.11",
- "rc-cascader": "~3.34.0",
- "rc-checkbox": "~3.5.0",
- "rc-collapse": "~3.9.0",
- "rc-dialog": "~9.6.0",
- "rc-drawer": "~7.3.0",
- "rc-dropdown": "~4.2.1",
- "rc-field-form": "~2.7.0",
- "rc-image": "~7.12.0",
- "rc-input": "~1.8.0",
- "rc-input-number": "~9.5.0",
- "rc-mentions": "~2.20.0",
- "rc-menu": "~9.16.1",
- "rc-motion": "^2.9.5",
- "rc-notification": "~5.6.4",
- "rc-pagination": "~5.1.0",
- "rc-picker": "~4.11.3",
- "rc-progress": "~4.0.0",
- "rc-rate": "~2.13.1",
- "rc-resize-observer": "^1.4.3",
- "rc-segmented": "~2.7.0",
- "rc-select": "~14.16.8",
- "rc-slider": "~11.1.8",
- "rc-steps": "~6.0.1",
- "rc-switch": "~4.1.0",
- "rc-table": "~7.51.1",
- "rc-tabs": "~15.7.0",
- "rc-textarea": "~1.10.2",
- "rc-tooltip": "~6.4.0",
- "rc-tree": "~5.13.1",
- "rc-tree-select": "~5.27.0",
- "rc-upload": "~4.9.2",
- "rc-util": "^5.44.4",
- "scroll-into-view-if-needed": "^3.1.0",
- "throttle-debounce": "^5.0.2"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/ant-design"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
- "license": "Python-2.0"
- },
- "node_modules/asn1.js": {
- "version": "5.4.1",
- "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
- "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bn.js": "^4.0.0",
- "inherits": "^2.0.1",
- "minimalistic-assert": "^1.0.0",
- "safer-buffer": "^2.1.0"
- }
- },
- "node_modules/bagpipe": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/bagpipe/-/bagpipe-0.3.5.tgz",
- "integrity": "sha512-42sAlmPDKes1nLm/aly+0VdaopSU9br+jkRELedhQxI5uXHgtk47I83Mpmf4zoNTRMASdLFtUkimlu/Z9zQ8+g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/binary-extensions": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
- "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/bn.js": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
- "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/body-parser": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
- "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bytes": "^3.1.2",
- "content-type": "^1.0.5",
- "debug": "^4.4.0",
- "http-errors": "^2.0.0",
- "iconv-lite": "^0.6.3",
- "on-finished": "^2.4.1",
- "qs": "^6.14.0",
- "raw-body": "^3.0.0",
- "type-is": "^2.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.1.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/browserslist": {
- "version": "4.25.2",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz",
- "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "caniuse-lite": "^1.0.30001733",
- "electron-to-chromium": "^1.5.199",
- "node-releases": "^2.0.19",
- "update-browserslist-db": "^1.1.3"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
- "node_modules/bytes": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001735",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz",
- "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "CC-BY-4.0"
- },
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
- "dev": true,
- "license": "MIT",
- "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"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/chokidar/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/chownr": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
- "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/classcat": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
- "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
- "license": "MIT"
- },
- "node_modules/classnames": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
- "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
- "license": "MIT"
- },
- "node_modules/clsx": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
- "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/commander": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz",
- "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/compute-scroll-into-view": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
- "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
- "license": "MIT"
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/content-disposition": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
- "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "5.2.1"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/content-type": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cookie": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
- "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/cookie-signature": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
- "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.6.0"
- }
- },
- "node_modules/copy-to-clipboard": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
- "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
- "license": "MIT",
- "dependencies": {
- "toggle-selection": "^1.0.6"
- }
- },
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/csstype": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "license": "MIT"
- },
- "node_modules/d3-array": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
- "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
- "license": "ISC",
- "dependencies": {
- "internmap": "1 - 2"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-color": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
- "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-dispatch": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
- "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-drag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
- "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
- "license": "ISC",
- "dependencies": {
- "d3-dispatch": "1 - 3",
- "d3-selection": "3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-ease": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
- "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-format": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
- "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-interpolate": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
- "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
- "license": "ISC",
- "dependencies": {
- "d3-color": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-path": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
- "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-scale": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
- "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
- "license": "ISC",
- "dependencies": {
- "d3-array": "2.10.0 - 3",
- "d3-format": "1 - 3",
- "d3-interpolate": "1.2.0 - 3",
- "d3-time": "2.1.1 - 3",
- "d3-time-format": "2 - 4"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-selection": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
- "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-shape": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
- "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
- "license": "ISC",
- "dependencies": {
- "d3-path": "^3.1.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-time": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
- "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
- "license": "ISC",
- "dependencies": {
- "d3-array": "2 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-time-format": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
- "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
- "license": "ISC",
- "dependencies": {
- "d3-time": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-timer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
- "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-transition": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
- "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
- "license": "ISC",
- "dependencies": {
- "d3-color": "1 - 3",
- "d3-dispatch": "1 - 3",
- "d3-ease": "1 - 3",
- "d3-interpolate": "1 - 3",
- "d3-timer": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- },
- "peerDependencies": {
- "d3-selection": "2 - 3"
- }
- },
- "node_modules/d3-zoom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
- "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
- "license": "ISC",
- "dependencies": {
- "d3-dispatch": "1 - 3",
- "d3-drag": "2 - 3",
- "d3-interpolate": "1 - 3",
- "d3-selection": "2 - 3",
- "d3-transition": "2 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/dayjs": {
- "version": "1.11.13",
- "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
- "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
- "license": "MIT"
- },
- "node_modules/debug": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
- "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/decimal.js-light": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
- "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
- "license": "MIT"
- },
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/detect-libc": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
- "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/dom-helpers": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
- "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.8.7",
- "csstype": "^3.0.2"
- }
- },
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/electron-to-chromium": {
- "version": "1.5.200",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.200.tgz",
- "integrity": "sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/encodeurl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
- "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/enhanced-resolve": {
- "version": "5.18.3",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
- "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.2.0"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/esbuild": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
- "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.9",
- "@esbuild/android-arm": "0.25.9",
- "@esbuild/android-arm64": "0.25.9",
- "@esbuild/android-x64": "0.25.9",
- "@esbuild/darwin-arm64": "0.25.9",
- "@esbuild/darwin-x64": "0.25.9",
- "@esbuild/freebsd-arm64": "0.25.9",
- "@esbuild/freebsd-x64": "0.25.9",
- "@esbuild/linux-arm": "0.25.9",
- "@esbuild/linux-arm64": "0.25.9",
- "@esbuild/linux-ia32": "0.25.9",
- "@esbuild/linux-loong64": "0.25.9",
- "@esbuild/linux-mips64el": "0.25.9",
- "@esbuild/linux-ppc64": "0.25.9",
- "@esbuild/linux-riscv64": "0.25.9",
- "@esbuild/linux-s390x": "0.25.9",
- "@esbuild/linux-x64": "0.25.9",
- "@esbuild/netbsd-arm64": "0.25.9",
- "@esbuild/netbsd-x64": "0.25.9",
- "@esbuild/openbsd-arm64": "0.25.9",
- "@esbuild/openbsd-x64": "0.25.9",
- "@esbuild/openharmony-arm64": "0.25.9",
- "@esbuild/sunos-x64": "0.25.9",
- "@esbuild/win32-arm64": "0.25.9",
- "@esbuild/win32-ia32": "0.25.9",
- "@esbuild/win32-x64": "0.25.9"
- }
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint": {
- "version": "9.33.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz",
- "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.0",
- "@eslint/config-helpers": "^0.3.1",
- "@eslint/core": "^0.15.2",
- "@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.33.0",
- "@eslint/plugin-kit": "^0.3.5",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.2",
- "@types/estree": "^1.0.6",
- "@types/json-schema": "^7.0.15",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.4.0",
- "eslint-visitor-keys": "^4.2.1",
- "espree": "^10.4.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- },
- "peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-plugin-react-hooks": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
- "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
- }
- },
- "node_modules/eslint-plugin-react-refresh": {
- "version": "0.4.20",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz",
- "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "eslint": ">=8.40"
- }
- },
- "node_modules/eslint-scope": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
- "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/espree": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
- "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "acorn": "^8.15.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "estraverse": "^5.1.0"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/eventemitter3": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
- "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
- "license": "MIT"
- },
- "node_modules/express": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
- "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "accepts": "^2.0.0",
- "body-parser": "^2.2.0",
- "content-disposition": "^1.0.0",
- "content-type": "^1.0.5",
- "cookie": "^0.7.1",
- "cookie-signature": "^1.2.1",
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "finalhandler": "^2.1.0",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.0",
- "merge-descriptors": "^2.0.0",
- "mime-types": "^3.0.0",
- "on-finished": "^2.4.1",
- "once": "^1.4.0",
- "parseurl": "^1.3.3",
- "proxy-addr": "^2.0.7",
- "qs": "^6.14.0",
- "range-parser": "^1.2.1",
- "router": "^2.2.0",
- "send": "^1.1.0",
- "serve-static": "^2.2.0",
- "statuses": "^2.0.1",
- "type-is": "^2.0.1",
- "vary": "^1.1.2"
- },
- "engines": {
- "node": ">= 18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/express-session": {
- "version": "1.18.2",
- "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz",
- "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cookie": "0.7.2",
- "cookie-signature": "1.0.7",
- "debug": "2.6.9",
- "depd": "~2.0.0",
- "on-headers": "~1.1.0",
- "parseurl": "~1.3.3",
- "safe-buffer": "5.2.1",
- "uid-safe": "~2.1.5"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/express-session/node_modules/cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/express-session/node_modules/cookie-signature": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
- "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/express-session/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/express-session/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/express/node_modules/cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-equals": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz",
- "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==",
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/fast-glob": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
- "license": "MIT",
- "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.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fastq": {
- "version": "1.19.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
- "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flat-cache": "^4.0.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/finalhandler": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
- "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "on-finished": "^2.4.1",
- "parseurl": "^1.3.3",
- "statuses": "^2.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/flatted": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
- "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/forwarded": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fresh": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
- "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/fs-extra": {
- "version": "11.3.1",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz",
- "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.0",
- "jsonfile": "^6.0.1",
- "universalify": "^2.0.0"
- },
- "engines": {
- "node": ">=14.14"
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "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.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/globals": {
- "version": "16.3.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
- "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/http-errors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "depd": "2.0.0",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "toidentifier": "1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/http-errors/node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/ignore-by-default": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
- "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/immer": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/immer/-/immer-11.0.1.tgz",
- "integrity": "sha512-naDCyggtcBWANtIrjQEajhhBEuL9b0Zg4zmlWK2CzS6xCWSE39/vvf4LqnMjUAWHBhot4m9MHCM/Z+mfWhUkiA==",
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/immer"
- }
- },
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.19"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/internmap": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
- "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/is-promise": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
- "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/is-typedarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/jiti": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
- "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jiti": "lib/jiti-cli.mjs"
- }
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "license": "MIT"
- },
- "node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json2mq": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
- "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
- "license": "MIT",
- "dependencies": {
- "string-convert": "^0.2.0"
- }
- },
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/jsonfile": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
- "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "universalify": "^2.0.0"
- },
- "optionalDependencies": {
- "graceful-fs": "^4.1.6"
- }
- },
- "node_modules/jssha": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz",
- "integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "json-buffer": "3.0.1"
- }
- },
- "node_modules/kruptein": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-2.2.3.tgz",
- "integrity": "sha512-BTwprBPTzkFT9oTugxKd3WnWrX630MqUDsnmBuoa98eQs12oD4n4TeI0GbpdGcYn/73Xueg2rfnw+oK4dovnJg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "asn1.js": "^5.4.1"
- },
- "engines": {
- "node": ">6"
- }
- },
- "node_modules/levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/lightningcss": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
- "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
- "dev": true,
- "license": "MPL-2.0",
- "dependencies": {
- "detect-libc": "^2.0.3"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- },
- "optionalDependencies": {
- "lightningcss-darwin-arm64": "1.30.1",
- "lightningcss-darwin-x64": "1.30.1",
- "lightningcss-freebsd-x64": "1.30.1",
- "lightningcss-linux-arm-gnueabihf": "1.30.1",
- "lightningcss-linux-arm64-gnu": "1.30.1",
- "lightningcss-linux-arm64-musl": "1.30.1",
- "lightningcss-linux-x64-gnu": "1.30.1",
- "lightningcss-linux-x64-musl": "1.30.1",
- "lightningcss-win32-arm64-msvc": "1.30.1",
- "lightningcss-win32-x64-msvc": "1.30.1"
- }
- },
- "node_modules/lightningcss-darwin-arm64": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
- "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-x64": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
- "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-freebsd-x64": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
- "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
- "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
- "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
- "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
- "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-musl": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
- "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
- "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
- "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "license": "MIT"
- },
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "license": "MIT",
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "yallist": "^3.0.2"
- }
- },
- "node_modules/lucide-react": {
- "version": "0.539.0",
- "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.539.0.tgz",
- "integrity": "sha512-VVISr+VF2krO91FeuCrm1rSOLACQUYVy7NQkzrOty52Y8TlTPcXcMdQFj9bYzBgXbWCiywlwSZ3Z8u6a+6bMlg==",
- "license": "ISC",
- "peerDependencies": {
- "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/magic-string": {
- "version": "0.30.17",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
- "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0"
- }
- },
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/media-typer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
- "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/merge-descriptors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
- "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/micromatch": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "braces": "^3.0.3",
- "picomatch": "^2.3.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/mime-db": {
- "version": "1.54.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
- "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
- "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mime-db": "^1.54.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/minimalistic-assert": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
- "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/minimist": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/minizlib": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
- "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "minipass": "^7.1.2"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/mkdirp": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
- "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "mkdirp": "dist/cjs/src/bin.js"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/mockjs": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/mockjs/-/mockjs-1.1.0.tgz",
- "integrity": "sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==",
- "dev": true,
- "dependencies": {
- "commander": "*"
- },
- "bin": {
- "random": "bin/random"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "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.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/negotiator": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
- "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/node-releases": {
- "version": "2.0.19",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
- "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/nodemon": {
- "version": "3.1.10",
- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
- "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chokidar": "^3.5.2",
- "debug": "^4",
- "ignore-by-default": "^1.0.1",
- "minimatch": "^3.1.2",
- "pstree.remy": "^1.1.8",
- "semver": "^7.5.3",
- "simple-update-notifier": "^2.0.0",
- "supports-color": "^5.5.0",
- "touch": "^3.1.0",
- "undefsafe": "^2.0.5"
- },
- "bin": {
- "nodemon": "bin/nodemon.js"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/nodemon"
- }
- },
- "node_modules/nodemon/node_modules/has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/nodemon/node_modules/semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/nodemon/node_modules/supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-inspect": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/on-finished": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
- "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ee-first": "1.1.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/on-headers": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
- "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/parseurl": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-to-regexp": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
- "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/postcss": {
- "version": "8.5.6",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
- "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.11",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- },
- "node_modules/prop-types/node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "license": "MIT"
- },
- "node_modules/proxy-addr": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/pstree.remy": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
- "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/qs": {
- "version": "6.14.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
- "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "side-channel": "^1.1.0"
- },
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/random-bytes": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
- "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/raw-body": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz",
- "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bytes": "3.1.2",
- "http-errors": "2.0.0",
- "iconv-lite": "0.7.0",
- "unpipe": "1.0.0"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/raw-body/node_modules/iconv-lite": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
- "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/rc-cascader": {
- "version": "3.34.0",
- "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz",
- "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.25.7",
- "classnames": "^2.3.1",
- "rc-select": "~14.16.2",
- "rc-tree": "~5.13.0",
- "rc-util": "^5.43.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-checkbox": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.5.0.tgz",
- "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "classnames": "^2.3.2",
- "rc-util": "^5.25.2"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-collapse": {
- "version": "3.9.0",
- "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz",
- "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "classnames": "2.x",
- "rc-motion": "^2.3.4",
- "rc-util": "^5.27.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-dialog": {
- "version": "9.6.0",
- "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz",
- "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "@rc-component/portal": "^1.0.0-8",
- "classnames": "^2.2.6",
- "rc-motion": "^2.3.0",
- "rc-util": "^5.21.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-drawer": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.3.0.tgz",
- "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.23.9",
- "@rc-component/portal": "^1.1.1",
- "classnames": "^2.2.6",
- "rc-motion": "^2.6.1",
- "rc-util": "^5.38.1"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-dropdown": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz",
- "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.18.3",
- "@rc-component/trigger": "^2.0.0",
- "classnames": "^2.2.6",
- "rc-util": "^5.44.1"
- },
- "peerDependencies": {
- "react": ">=16.11.0",
- "react-dom": ">=16.11.0"
- }
- },
- "node_modules/rc-field-form": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.0.tgz",
- "integrity": "sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.18.0",
- "@rc-component/async-validator": "^5.0.3",
- "rc-util": "^5.32.2"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-image": {
- "version": "7.12.0",
- "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.12.0.tgz",
- "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.11.2",
- "@rc-component/portal": "^1.0.2",
- "classnames": "^2.2.6",
- "rc-dialog": "~9.6.0",
- "rc-motion": "^2.6.2",
- "rc-util": "^5.34.1"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-input": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.8.0.tgz",
- "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.11.1",
- "classnames": "^2.2.1",
- "rc-util": "^5.18.1"
- },
- "peerDependencies": {
- "react": ">=16.0.0",
- "react-dom": ">=16.0.0"
- }
- },
- "node_modules/rc-input-number": {
- "version": "9.5.0",
- "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.5.0.tgz",
- "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "@rc-component/mini-decimal": "^1.0.1",
- "classnames": "^2.2.5",
- "rc-input": "~1.8.0",
- "rc-util": "^5.40.1"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-mentions": {
- "version": "2.20.0",
- "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.20.0.tgz",
- "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.22.5",
- "@rc-component/trigger": "^2.0.0",
- "classnames": "^2.2.6",
- "rc-input": "~1.8.0",
- "rc-menu": "~9.16.0",
- "rc-textarea": "~1.10.0",
- "rc-util": "^5.34.1"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-menu": {
- "version": "9.16.1",
- "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz",
- "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "@rc-component/trigger": "^2.0.0",
- "classnames": "2.x",
- "rc-motion": "^2.4.3",
- "rc-overflow": "^1.3.1",
- "rc-util": "^5.27.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-motion": {
- "version": "2.9.5",
- "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz",
- "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.11.1",
- "classnames": "^2.2.1",
- "rc-util": "^5.44.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-notification": {
- "version": "5.6.4",
- "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.4.tgz",
- "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "classnames": "2.x",
- "rc-motion": "^2.9.0",
- "rc-util": "^5.20.1"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-overflow": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.4.1.tgz",
- "integrity": "sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.11.1",
- "classnames": "^2.2.1",
- "rc-resize-observer": "^1.0.0",
- "rc-util": "^5.37.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-pagination": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.1.0.tgz",
- "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "classnames": "^2.3.2",
- "rc-util": "^5.38.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-picker": {
- "version": "4.11.3",
- "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.11.3.tgz",
- "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.24.7",
- "@rc-component/trigger": "^2.0.0",
- "classnames": "^2.2.1",
- "rc-overflow": "^1.3.2",
- "rc-resize-observer": "^1.4.0",
- "rc-util": "^5.43.0"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "date-fns": ">= 2.x",
- "dayjs": ">= 1.x",
- "luxon": ">= 3.x",
- "moment": ">= 2.x",
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- },
- "peerDependenciesMeta": {
- "date-fns": {
- "optional": true
- },
- "dayjs": {
- "optional": true
- },
- "luxon": {
- "optional": true
- },
- "moment": {
- "optional": true
- }
- }
- },
- "node_modules/rc-progress": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz",
- "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "classnames": "^2.2.6",
- "rc-util": "^5.16.1"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-rate": {
- "version": "2.13.1",
- "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.1.tgz",
- "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "classnames": "^2.2.5",
- "rc-util": "^5.0.1"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-resize-observer": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz",
- "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.20.7",
- "classnames": "^2.2.1",
- "rc-util": "^5.44.1",
- "resize-observer-polyfill": "^1.5.1"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-segmented": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.0.tgz",
- "integrity": "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.11.1",
- "classnames": "^2.2.1",
- "rc-motion": "^2.4.4",
- "rc-util": "^5.17.0"
- },
- "peerDependencies": {
- "react": ">=16.0.0",
- "react-dom": ">=16.0.0"
- }
- },
- "node_modules/rc-select": {
- "version": "14.16.8",
- "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz",
- "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "@rc-component/trigger": "^2.1.1",
- "classnames": "2.x",
- "rc-motion": "^2.0.1",
- "rc-overflow": "^1.3.1",
- "rc-util": "^5.16.1",
- "rc-virtual-list": "^3.5.2"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": "*",
- "react-dom": "*"
- }
- },
- "node_modules/rc-slider": {
- "version": "11.1.8",
- "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.8.tgz",
- "integrity": "sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "classnames": "^2.2.5",
- "rc-util": "^5.36.0"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-steps": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz",
- "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.16.7",
- "classnames": "^2.2.3",
- "rc-util": "^5.16.1"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-switch": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz",
- "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.21.0",
- "classnames": "^2.2.1",
- "rc-util": "^5.30.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-table": {
- "version": "7.51.1",
- "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.51.1.tgz",
- "integrity": "sha512-5iq15mTHhvC42TlBLRCoCBLoCmGlbRZAlyF21FonFnS/DIC8DeRqnmdyVREwt2CFbPceM0zSNdEeVfiGaqYsKw==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "@rc-component/context": "^1.4.0",
- "classnames": "^2.2.5",
- "rc-resize-observer": "^1.1.0",
- "rc-util": "^5.44.3",
- "rc-virtual-list": "^3.14.2"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-tabs": {
- "version": "15.7.0",
- "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.7.0.tgz",
- "integrity": "sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.11.2",
- "classnames": "2.x",
- "rc-dropdown": "~4.2.0",
- "rc-menu": "~9.16.0",
- "rc-motion": "^2.6.2",
- "rc-resize-observer": "^1.0.0",
- "rc-util": "^5.34.1"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-textarea": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.2.tgz",
- "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "classnames": "^2.2.1",
- "rc-input": "~1.8.0",
- "rc-resize-observer": "^1.0.0",
- "rc-util": "^5.27.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-tooltip": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.4.0.tgz",
- "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.11.2",
- "@rc-component/trigger": "^2.0.0",
- "classnames": "^2.3.1",
- "rc-util": "^5.44.3"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-tree": {
- "version": "5.13.1",
- "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz",
- "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.10.1",
- "classnames": "2.x",
- "rc-motion": "^2.0.1",
- "rc-util": "^5.16.1",
- "rc-virtual-list": "^3.5.1"
- },
- "engines": {
- "node": ">=10.x"
- },
- "peerDependencies": {
- "react": "*",
- "react-dom": "*"
- }
- },
- "node_modules/rc-tree-select": {
- "version": "5.27.0",
- "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.27.0.tgz",
- "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.25.7",
- "classnames": "2.x",
- "rc-select": "~14.16.2",
- "rc-tree": "~5.13.0",
- "rc-util": "^5.43.0"
- },
- "peerDependencies": {
- "react": "*",
- "react-dom": "*"
- }
- },
- "node_modules/rc-upload": {
- "version": "4.9.2",
- "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.9.2.tgz",
- "integrity": "sha512-nHx+9rbd1FKMiMRYsqQ3NkXUv7COHPBo3X1Obwq9SWS6/diF/A0aJ5OHubvwUAIDs+4RMleljV0pcrNUc823GQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.18.3",
- "classnames": "^2.2.5",
- "rc-util": "^5.2.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-util": {
- "version": "5.44.4",
- "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz",
- "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.18.3",
- "react-is": "^18.2.0"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/rc-virtual-list": {
- "version": "3.19.1",
- "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.19.1.tgz",
- "integrity": "sha512-DCapO2oyPqmooGhxBuXHM4lFuX+sshQwWqqkuyFA+4rShLe//+GEPVwiDgO+jKtKHtbeYwZoNvetwfHdOf+iUQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.20.0",
- "classnames": "^2.2.6",
- "rc-resize-observer": "^1.0.0",
- "rc-util": "^5.36.0"
- },
- "engines": {
- "node": ">=8.x"
- },
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
- }
- },
- "node_modules/react": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
- "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
- "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.23.2"
- },
- "peerDependencies": {
- "react": "^18.3.1"
- }
- },
- "node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "license": "MIT"
- },
- "node_modules/react-redux": {
- "version": "9.2.0",
- "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
- "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
- "license": "MIT",
- "dependencies": {
- "@types/use-sync-external-store": "^0.0.6",
- "use-sync-external-store": "^1.4.0"
- },
- "peerDependencies": {
- "@types/react": "^18.2.25 || ^19",
- "react": "^18.0 || ^19",
- "redux": "^5.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "redux": {
- "optional": true
- }
- }
- },
- "node_modules/react-refresh": {
- "version": "0.17.0",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
- "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-router": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.0.tgz",
- "integrity": "sha512-r15M3+LHKgM4SOapNmsH3smAizWds1vJ0Z9C4mWaKnT9/wD7+d/0jYcj6LmOvonkrO4Rgdyp4KQ/29gWN2i1eg==",
- "license": "MIT",
- "dependencies": {
- "cookie": "^1.0.1",
- "set-cookie-parser": "^2.6.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
- },
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/react-smooth": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
- "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
- "license": "MIT",
- "dependencies": {
- "fast-equals": "^5.0.1",
- "prop-types": "^15.8.1",
- "react-transition-group": "^4.4.5"
- },
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
- "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/react-transition-group": {
- "version": "4.4.5",
- "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
- "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@babel/runtime": "^7.5.5",
- "dom-helpers": "^5.0.1",
- "loose-envify": "^1.4.0",
- "prop-types": "^15.6.2"
- },
- "peerDependencies": {
- "react": ">=16.6.0",
- "react-dom": ">=16.6.0"
- }
- },
- "node_modules/readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "picomatch": "^2.2.1"
- },
- "engines": {
- "node": ">=8.10.0"
- }
- },
- "node_modules/recharts": {
- "version": "2.15.0",
- "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.0.tgz",
- "integrity": "sha512-cIvMxDfpAmqAmVgc4yb7pgm/O1tmmkl/CjrvXuW+62/+7jj/iF9Ykm+hb/UJt42TREHMyd3gb+pkgoa2MxgDIw==",
- "license": "MIT",
- "dependencies": {
- "clsx": "^2.0.0",
- "eventemitter3": "^4.0.1",
- "lodash": "^4.17.21",
- "react-is": "^18.3.1",
- "react-smooth": "^4.0.0",
- "recharts-scale": "^0.4.4",
- "tiny-invariant": "^1.3.1",
- "victory-vendor": "^36.6.8"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
- "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/recharts-scale": {
- "version": "0.4.5",
- "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
- "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
- "license": "MIT",
- "dependencies": {
- "decimal.js-light": "^2.4.1"
- }
- },
- "node_modules/redux": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
- "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
- "license": "MIT"
- },
- "node_modules/redux-thunk": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
- "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
- "license": "MIT",
- "peerDependencies": {
- "redux": "^5.0.0"
- }
- },
- "node_modules/reselect": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
- "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
- "license": "MIT"
- },
- "node_modules/resize-observer-polyfill": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
- "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==",
- "license": "MIT"
- },
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/retry": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
- "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/reusify": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
- "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
- "node_modules/rollup": {
- "version": "4.46.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz",
- "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "1.0.8"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.46.2",
- "@rollup/rollup-android-arm64": "4.46.2",
- "@rollup/rollup-darwin-arm64": "4.46.2",
- "@rollup/rollup-darwin-x64": "4.46.2",
- "@rollup/rollup-freebsd-arm64": "4.46.2",
- "@rollup/rollup-freebsd-x64": "4.46.2",
- "@rollup/rollup-linux-arm-gnueabihf": "4.46.2",
- "@rollup/rollup-linux-arm-musleabihf": "4.46.2",
- "@rollup/rollup-linux-arm64-gnu": "4.46.2",
- "@rollup/rollup-linux-arm64-musl": "4.46.2",
- "@rollup/rollup-linux-loongarch64-gnu": "4.46.2",
- "@rollup/rollup-linux-ppc64-gnu": "4.46.2",
- "@rollup/rollup-linux-riscv64-gnu": "4.46.2",
- "@rollup/rollup-linux-riscv64-musl": "4.46.2",
- "@rollup/rollup-linux-s390x-gnu": "4.46.2",
- "@rollup/rollup-linux-x64-gnu": "4.46.2",
- "@rollup/rollup-linux-x64-musl": "4.46.2",
- "@rollup/rollup-win32-arm64-msvc": "4.46.2",
- "@rollup/rollup-win32-ia32-msvc": "4.46.2",
- "@rollup/rollup-win32-x64-msvc": "4.46.2",
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/router": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
- "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^4.4.0",
- "depd": "^2.0.0",
- "is-promise": "^4.0.0",
- "parseurl": "^1.3.3",
- "path-to-regexp": "^8.0.0"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "queue-microtask": "^1.2.2"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/scheduler": {
- "version": "0.23.2",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
- "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0"
- }
- },
- "node_modules/scroll-into-view-if-needed": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
- "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
- "license": "MIT",
- "dependencies": {
- "compute-scroll-into-view": "^3.0.2"
- }
- },
- "node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/send": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
- "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^4.3.5",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.0",
- "mime-types": "^3.0.1",
- "ms": "^2.1.3",
- "on-finished": "^2.4.1",
- "range-parser": "^1.2.1",
- "statuses": "^2.0.1"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/serve-static": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
- "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "parseurl": "^1.3.3",
- "send": "^1.2.0"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/session-file-store": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/session-file-store/-/session-file-store-1.5.0.tgz",
- "integrity": "sha512-60IZaJNzyu2tIeHutkYE8RiXVx3KRvacOxfLr2Mj92SIsRIroDsH0IlUUR6fJAjoTW4RQISbaOApa2IZpIwFdQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "bagpipe": "^0.3.5",
- "fs-extra": "^8.0.1",
- "kruptein": "^2.0.4",
- "object-assign": "^4.1.1",
- "retry": "^0.12.0",
- "write-file-atomic": "3.0.3"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/session-file-store/node_modules/fs-extra": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
- "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.0",
- "jsonfile": "^4.0.0",
- "universalify": "^0.1.0"
- },
- "engines": {
- "node": ">=6 <7 || >=8"
- }
- },
- "node_modules/session-file-store/node_modules/jsonfile": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
- "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
- "dev": true,
- "license": "MIT",
- "optionalDependencies": {
- "graceful-fs": "^4.1.6"
- }
- },
- "node_modules/session-file-store/node_modules/universalify": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
- "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4.0.0"
- }
- },
- "node_modules/set-cookie-parser": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
- "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
- "license": "MIT"
- },
- "node_modules/setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/simple-update-notifier": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
- "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.5.3"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/simple-update-notifier/node_modules/semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/statuses": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
- "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/string-convert": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
- "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==",
- "license": "MIT"
- },
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/stylis": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
- "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
- "license": "MIT"
- },
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/tailwindcss": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz",
- "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/tapable": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
- "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/tar": {
- "version": "7.4.3",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
- "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@isaacs/fs-minipass": "^4.0.0",
- "chownr": "^3.0.0",
- "minipass": "^7.1.2",
- "minizlib": "^3.0.1",
- "mkdirp": "^3.0.1",
- "yallist": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tar/node_modules/yallist": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
- "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/throttle-debounce": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
- "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
- "license": "MIT",
- "engines": {
- "node": ">=12.22"
- }
- },
- "node_modules/tiny-invariant": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
- "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
- "license": "MIT"
- },
- "node_modules/tinyglobby": {
- "version": "0.2.14",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
- "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fdir": "^6.4.4",
- "picomatch": "^4.0.2"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
- }
- },
- "node_modules/tinyglobby/node_modules/fdir": {
- "version": "6.4.6",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
- "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/toggle-selection": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
- "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
- "license": "MIT"
- },
- "node_modules/toidentifier": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/touch": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
- "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "nodetouch": "bin/nodetouch.js"
- }
- },
- "node_modules/ts-api-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
- "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.12"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4"
- }
- },
- "node_modules/type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/type-is": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
- "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "content-type": "^1.0.5",
- "media-typer": "^1.1.0",
- "mime-types": "^3.0.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/typedarray-to-buffer": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
- "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-typedarray": "^1.0.0"
- }
- },
- "node_modules/typescript": {
- "version": "5.8.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
- "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/typescript-eslint": {
- "version": "8.39.1",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.1.tgz",
- "integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/eslint-plugin": "8.39.1",
- "@typescript-eslint/parser": "8.39.1",
- "@typescript-eslint/typescript-estree": "8.39.1",
- "@typescript-eslint/utils": "8.39.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/uid-safe": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
- "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "random-bytes": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/undefsafe": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
- "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/undici-types": {
- "version": "7.10.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
- "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/universalify": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
- "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 10.0.0"
- }
- },
- "node_modules/unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/update-browserslist-db": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
- "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
- }
- },
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
- "node_modules/use-sync-external-store": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
- "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
- "license": "MIT",
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/vary": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/victory-vendor": {
- "version": "36.9.2",
- "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
- "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
- "license": "MIT AND ISC",
- "dependencies": {
- "@types/d3-array": "^3.0.3",
- "@types/d3-ease": "^3.0.0",
- "@types/d3-interpolate": "^3.0.1",
- "@types/d3-scale": "^4.0.2",
- "@types/d3-shape": "^3.1.0",
- "@types/d3-time": "^3.0.0",
- "@types/d3-timer": "^3.0.0",
- "d3-array": "^3.1.6",
- "d3-ease": "^3.0.1",
- "d3-interpolate": "^3.0.1",
- "d3-scale": "^4.0.2",
- "d3-shape": "^3.1.0",
- "d3-time": "^3.0.0",
- "d3-timer": "^3.0.1"
- }
- },
- "node_modules/vite": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz",
- "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "esbuild": "^0.25.0",
- "fdir": "^6.4.6",
- "picomatch": "^4.0.3",
- "postcss": "^8.5.6",
- "rollup": "^4.43.0",
- "tinyglobby": "^0.2.14"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^20.19.0 || >=22.12.0",
- "jiti": ">=1.21.0",
- "less": "^4.0.0",
- "lightningcss": "^1.21.0",
- "sass": "^1.70.0",
- "sass-embedded": "^1.70.0",
- "stylus": ">=0.54.8",
- "sugarss": "^5.0.0",
- "terser": "^5.16.0",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "jiti": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
- },
- "node_modules/vite/node_modules/fdir": {
- "version": "6.4.6",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
- "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/vite/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/word-wrap": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
- "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/write-file-atomic": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
- "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "imurmurhash": "^0.1.4",
- "is-typedarray": "^1.0.0",
- "signal-exit": "^3.0.2",
- "typedarray-to-buffer": "^3.1.5"
- }
- },
- "node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/zustand": {
- "version": "4.5.7",
- "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
- "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
- "license": "MIT",
- "dependencies": {
- "use-sync-external-store": "^1.2.2"
- },
- "engines": {
- "node": ">=12.7.0"
- },
- "peerDependencies": {
- "@types/react": ">=16.8",
- "immer": ">=9.0.6",
- "react": ">=16.8"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "immer": {
- "optional": true
- },
- "react": {
- "optional": true
- }
- }
- }
- }
-}
+{
+ "name": "edatamate",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "edatamate",
+ "version": "0.0.0",
+ "dependencies": {
+ "@reduxjs/toolkit": "^2.11.0",
+ "@xyflow/react": "^12.8.3",
+ "antd": "^5.27.0",
+ "jssha": "^3.3.1",
+ "lucide-react": "^0.539.0",
+ "react": "^18.1.1",
+ "react-dom": "^18.1.1",
+ "react-redux": "^9.2.0",
+ "react-router": "^7.8.0",
+ "recharts": "2.15.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.33.0",
+ "@tailwindcss/vite": "^4.1.12",
+ "@types/node": "^24.2.1",
+ "@types/react": "^18.1.10",
+ "@types/react-dom": "^18.1.7",
+ "@vitejs/plugin-react": "^5.0.0",
+ "body-parser": "^2.2.0",
+ "eslint": "^9.33.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.20",
+ "express": "^5.1.0",
+ "express-session": "^1.18.2",
+ "fs-extra": "^11.3.1",
+ "globals": "^16.3.0",
+ "lodash": "^4.17.21",
+ "minimist": "^1.2.8",
+ "mockjs": "^1.1.0",
+ "nodemon": "^3.1.10",
+ "postcss": "^8.5.6",
+ "session-file-store": "^1.5.0",
+ "tailwindcss": "^4.1.12",
+ "typescript": "~5.8.3",
+ "typescript-eslint": "^8.39.1",
+ "vite": "^7.1.2"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@ant-design/colors": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz",
+ "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/fast-color": "^2.0.6"
+ }
+ },
+ "node_modules/@ant-design/cssinjs": {
+ "version": "1.24.0",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.24.0.tgz",
+ "integrity": "sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "@emotion/hash": "^0.8.0",
+ "@emotion/unitless": "^0.7.5",
+ "classnames": "^2.3.1",
+ "csstype": "^3.1.3",
+ "rc-util": "^5.35.0",
+ "stylis": "^4.3.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@ant-design/cssinjs-utils": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz",
+ "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/cssinjs": "^1.21.0",
+ "@babel/runtime": "^7.23.2",
+ "rc-util": "^5.38.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@ant-design/fast-color": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz",
+ "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
+ "node_modules/@ant-design/icons": {
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz",
+ "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/colors": "^7.0.0",
+ "@ant-design/icons-svg": "^4.4.0",
+ "@babel/runtime": "^7.24.8",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.31.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@ant-design/icons-svg": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
+ "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==",
+ "license": "MIT"
+ },
+ "node_modules/@ant-design/react-slick": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz",
+ "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.4",
+ "classnames": "^2.2.5",
+ "json2mq": "^0.2.0",
+ "resize-observer-polyfill": "^1.5.1",
+ "throttle-debounce": "^5.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
+ "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
+ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.0",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.27.3",
+ "@babel/helpers": "^7.27.6",
+ "@babel/parser": "^7.28.0",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.0",
+ "@babel/types": "^7.28.0",
+ "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"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
+ "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.0",
+ "@babel/types": "^7.28.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
+ "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.27.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz",
+ "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
+ "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz",
+ "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
+ "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.0",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
+ "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
+ "license": "MIT"
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz",
+ "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz",
+ "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz",
+ "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz",
+ "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz",
+ "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz",
+ "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz",
+ "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz",
+ "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz",
+ "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz",
+ "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz",
+ "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz",
+ "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz",
+ "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz",
+ "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz",
+ "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz",
+ "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz",
+ "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz",
+ "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz",
+ "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz",
+ "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz",
+ "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz",
+ "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz",
+ "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+ "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
+ "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.6",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
+ "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
+ "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.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": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.33.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz",
+ "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+ "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
+ "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.15.2",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.6",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+ "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@isaacs/fs-minipass": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.30",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
+ "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "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.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@rc-component/async-validator": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz",
+ "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.4"
+ },
+ "engines": {
+ "node": ">=14.x"
+ }
+ },
+ "node_modules/@rc-component/color-picker": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz",
+ "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/fast-color": "^2.0.6",
+ "@babel/runtime": "^7.23.6",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.38.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/context": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz",
+ "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "rc-util": "^5.27.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/mini-decimal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz",
+ "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
+ "node_modules/@rc-component/mutate-observer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz",
+ "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.24.4"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/portal": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz",
+ "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.24.4"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/qrcode": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.0.0.tgz",
+ "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.38.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/tour": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz",
+ "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0",
+ "@rc-component/portal": "^1.0.0-9",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.24.4"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/trigger": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.3.0.tgz",
+ "integrity": "sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2",
+ "@rc-component/portal": "^1.1.0",
+ "classnames": "^2.3.2",
+ "rc-motion": "^2.0.0",
+ "rc-resize-observer": "^1.3.1",
+ "rc-util": "^5.44.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@reduxjs/toolkit": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.0.tgz",
+ "integrity": "sha512-hBjYg0aaRL1O2Z0IqWhnTLytnjDIxekmRxm1snsHjHaKVmIF1HiImWqsq+PuEbn6zdMlkIj9WofK1vR8jjx+Xw==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@standard-schema/utils": "^0.3.0",
+ "immer": "^11.0.0",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0",
+ "reselect": "^5.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+ "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.30",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.30.tgz",
+ "integrity": "sha512-whXaSoNUFiyDAjkUF8OBpOm77Szdbk5lGNqFe6CbVbJFrhCCPinCbRA3NjawwlNHla1No7xvXXh+CpSxnPfUEw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz",
+ "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz",
+ "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz",
+ "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz",
+ "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz",
+ "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz",
+ "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz",
+ "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz",
+ "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz",
+ "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz",
+ "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz",
+ "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz",
+ "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz",
+ "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz",
+ "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz",
+ "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz",
+ "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz",
+ "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz",
+ "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz",
+ "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz",
+ "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+ "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "license": "MIT"
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.12.tgz",
+ "integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.5.1",
+ "lightningcss": "1.30.1",
+ "magic-string": "^0.30.17",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.12"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.12.tgz",
+ "integrity": "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.4",
+ "tar": "^7.4.3"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.12",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.12",
+ "@tailwindcss/oxide-darwin-x64": "4.1.12",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.12",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.12",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.12",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.12",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.12",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.12"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz",
+ "integrity": "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz",
+ "integrity": "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz",
+ "integrity": "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz",
+ "integrity": "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz",
+ "integrity": "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz",
+ "integrity": "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz",
+ "integrity": "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.12.tgz",
+ "integrity": "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.12.tgz",
+ "integrity": "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz",
+ "integrity": "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.5",
+ "@emnapi/runtime": "^1.4.5",
+ "@emnapi/wasi-threads": "^1.0.4",
+ "@napi-rs/wasm-runtime": "^0.2.12",
+ "@tybys/wasm-util": "^0.10.0",
+ "tslib": "^2.8.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz",
+ "integrity": "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz",
+ "integrity": "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.12.tgz",
+ "integrity": "sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.1.12",
+ "@tailwindcss/oxide": "4.1.12",
+ "tailwindcss": "4.1.12"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
+ "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-drag": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+ "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-selection": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
+ "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
+ "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-transition": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
+ "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-zoom": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+ "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-interpolate": "*",
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.2.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz",
+ "integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.10.0"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.23",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
+ "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz",
+ "integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.39.1",
+ "@typescript-eslint/type-utils": "8.39.1",
+ "@typescript-eslint/utils": "8.39.1",
+ "@typescript-eslint/visitor-keys": "8.39.1",
+ "graphemer": "^1.4.0",
+ "ignore": "^7.0.0",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.39.1",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.1.tgz",
+ "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.39.1",
+ "@typescript-eslint/types": "8.39.1",
+ "@typescript-eslint/typescript-estree": "8.39.1",
+ "@typescript-eslint/visitor-keys": "8.39.1",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.1.tgz",
+ "integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.39.1",
+ "@typescript-eslint/types": "^8.39.1",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz",
+ "integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.39.1",
+ "@typescript-eslint/visitor-keys": "8.39.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz",
+ "integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz",
+ "integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.39.1",
+ "@typescript-eslint/typescript-estree": "8.39.1",
+ "@typescript-eslint/utils": "8.39.1",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz",
+ "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz",
+ "integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.39.1",
+ "@typescript-eslint/tsconfig-utils": "8.39.1",
+ "@typescript-eslint/types": "8.39.1",
+ "@typescript-eslint/visitor-keys": "8.39.1",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.1.tgz",
+ "integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.39.1",
+ "@typescript-eslint/types": "8.39.1",
+ "@typescript-eslint/typescript-estree": "8.39.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz",
+ "integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.39.1",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.0.tgz",
+ "integrity": "sha512-Jx9JfsTa05bYkS9xo0hkofp2dCmp1blrKjw9JONs5BTHOvJCgLbaPSuZLGSVJW6u2qe0tc4eevY0+gSNNi0YCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.30",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@xyflow/react": {
+ "version": "12.8.3",
+ "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.3.tgz",
+ "integrity": "sha512-8sdRZPMCzfhauF96krlUMPCKmi9cX64HsYG8qoVAAvTKDAqxXg7RSp/IhoXlzbI/lsRD1vAxeDBxvI/XqACa6g==",
+ "license": "MIT",
+ "dependencies": {
+ "@xyflow/system": "0.0.67",
+ "classcat": "^5.0.3",
+ "zustand": "^4.4.0"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@xyflow/system": {
+ "version": "0.0.67",
+ "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.67.tgz",
+ "integrity": "sha512-hYsmbj+8JDei0jmupBmxNLaeJEcf9kKmMl6IziGe02i0TOCsHwjIdP+qz+f4rI1/FR2CQiCZJrw4dkHOLC6tEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-drag": "^3.0.7",
+ "@types/d3-interpolate": "^3.0.4",
+ "@types/d3-selection": "^3.0.10",
+ "@types/d3-transition": "^3.0.8",
+ "@types/d3-zoom": "^3.0.8",
+ "d3-drag": "^3.0.0",
+ "d3-interpolate": "^3.0.1",
+ "d3-selection": "^3.0.0",
+ "d3-zoom": "^3.0.0"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "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"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/antd": {
+ "version": "5.27.0",
+ "resolved": "https://registry.npmjs.org/antd/-/antd-5.27.0.tgz",
+ "integrity": "sha512-o54dmpooLOc08RSGCkeEQBYAGPxUSmnhmYJKCNTHH46vzjOVxdteu+wPTRVkRbAkDTbs2VcNr5VL7Lu67rPIiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/colors": "^7.2.1",
+ "@ant-design/cssinjs": "^1.23.0",
+ "@ant-design/cssinjs-utils": "^1.1.3",
+ "@ant-design/fast-color": "^2.0.6",
+ "@ant-design/icons": "^5.6.1",
+ "@ant-design/react-slick": "~1.1.2",
+ "@babel/runtime": "^7.26.0",
+ "@rc-component/color-picker": "~2.0.1",
+ "@rc-component/mutate-observer": "^1.1.0",
+ "@rc-component/qrcode": "~1.0.0",
+ "@rc-component/tour": "~1.15.1",
+ "@rc-component/trigger": "^2.3.0",
+ "classnames": "^2.5.1",
+ "copy-to-clipboard": "^3.3.3",
+ "dayjs": "^1.11.11",
+ "rc-cascader": "~3.34.0",
+ "rc-checkbox": "~3.5.0",
+ "rc-collapse": "~3.9.0",
+ "rc-dialog": "~9.6.0",
+ "rc-drawer": "~7.3.0",
+ "rc-dropdown": "~4.2.1",
+ "rc-field-form": "~2.7.0",
+ "rc-image": "~7.12.0",
+ "rc-input": "~1.8.0",
+ "rc-input-number": "~9.5.0",
+ "rc-mentions": "~2.20.0",
+ "rc-menu": "~9.16.1",
+ "rc-motion": "^2.9.5",
+ "rc-notification": "~5.6.4",
+ "rc-pagination": "~5.1.0",
+ "rc-picker": "~4.11.3",
+ "rc-progress": "~4.0.0",
+ "rc-rate": "~2.13.1",
+ "rc-resize-observer": "^1.4.3",
+ "rc-segmented": "~2.7.0",
+ "rc-select": "~14.16.8",
+ "rc-slider": "~11.1.8",
+ "rc-steps": "~6.0.1",
+ "rc-switch": "~4.1.0",
+ "rc-table": "~7.51.1",
+ "rc-tabs": "~15.7.0",
+ "rc-textarea": "~1.10.2",
+ "rc-tooltip": "~6.4.0",
+ "rc-tree": "~5.13.1",
+ "rc-tree-select": "~5.27.0",
+ "rc-upload": "~4.9.2",
+ "rc-util": "^5.44.4",
+ "scroll-into-view-if-needed": "^3.1.0",
+ "throttle-debounce": "^5.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ant-design"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/asn1.js": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
+ "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bn.js": "^4.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "node_modules/bagpipe": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/bagpipe/-/bagpipe-0.3.5.tgz",
+ "integrity": "sha512-42sAlmPDKes1nLm/aly+0VdaopSU9br+jkRELedhQxI5uXHgtk47I83Mpmf4zoNTRMASdLFtUkimlu/Z9zQ8+g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/bn.js": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
+ "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/body-parser": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
+ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.0",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.6.3",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.0",
+ "raw-body": "^3.0.0",
+ "type-is": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.25.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz",
+ "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001733",
+ "electron-to-chromium": "^1.5.199",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001735",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz",
+ "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "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"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/classcat": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
+ "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
+ "license": "MIT"
+ },
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
+ "license": "MIT"
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/commander": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz",
+ "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/compute-scroll-into-view": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
+ "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/content-disposition": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
+ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/copy-to-clipboard": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
+ "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
+ "license": "MIT",
+ "dependencies": {
+ "toggle-selection": "^1.0.6"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
+ },
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "license": "ISC",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dispatch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+ "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-drag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
+ "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-selection": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
+ "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-selection": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
+ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-transition": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
+ "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-dispatch": "1 - 3",
+ "d3-ease": "1 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "d3-selection": "2 - 3"
+ }
+ },
+ "node_modules/d3-zoom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
+ "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "2 - 3",
+ "d3-transition": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.13",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
+ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js-light": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
+ "license": "MIT"
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
+ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.200",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.200.tgz",
+ "integrity": "sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.3",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
+ "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
+ "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.9",
+ "@esbuild/android-arm": "0.25.9",
+ "@esbuild/android-arm64": "0.25.9",
+ "@esbuild/android-x64": "0.25.9",
+ "@esbuild/darwin-arm64": "0.25.9",
+ "@esbuild/darwin-x64": "0.25.9",
+ "@esbuild/freebsd-arm64": "0.25.9",
+ "@esbuild/freebsd-x64": "0.25.9",
+ "@esbuild/linux-arm": "0.25.9",
+ "@esbuild/linux-arm64": "0.25.9",
+ "@esbuild/linux-ia32": "0.25.9",
+ "@esbuild/linux-loong64": "0.25.9",
+ "@esbuild/linux-mips64el": "0.25.9",
+ "@esbuild/linux-ppc64": "0.25.9",
+ "@esbuild/linux-riscv64": "0.25.9",
+ "@esbuild/linux-s390x": "0.25.9",
+ "@esbuild/linux-x64": "0.25.9",
+ "@esbuild/netbsd-arm64": "0.25.9",
+ "@esbuild/netbsd-x64": "0.25.9",
+ "@esbuild/openbsd-arm64": "0.25.9",
+ "@esbuild/openbsd-x64": "0.25.9",
+ "@esbuild/openharmony-arm64": "0.25.9",
+ "@esbuild/sunos-x64": "0.25.9",
+ "@esbuild/win32-arm64": "0.25.9",
+ "@esbuild/win32-ia32": "0.25.9",
+ "@esbuild/win32-x64": "0.25.9"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.33.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz",
+ "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.0",
+ "@eslint/config-helpers": "^0.3.1",
+ "@eslint/core": "^0.15.2",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.33.0",
+ "@eslint/plugin-kit": "^0.3.5",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
+ "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.20",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz",
+ "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "license": "MIT"
+ },
+ "node_modules/express": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.0",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-session": {
+ "version": "1.18.2",
+ "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz",
+ "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "0.7.2",
+ "cookie-signature": "1.0.7",
+ "debug": "2.6.9",
+ "depd": "~2.0.0",
+ "on-headers": "~1.1.0",
+ "parseurl": "~1.3.3",
+ "safe-buffer": "5.2.1",
+ "uid-safe": "~2.1.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/express-session/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express-session/node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/express-session/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express-session/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/express/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-equals": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz",
+ "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "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.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "11.3.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz",
+ "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "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.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
+ "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/http-errors/node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/immer": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-11.0.1.tgz",
+ "integrity": "sha512-naDCyggtcBWANtIrjQEajhhBEuL9b0Zg4zmlWK2CzS6xCWSE39/vvf4LqnMjUAWHBhot4m9MHCM/Z+mfWhUkiA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
+ "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json2mq": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
+ "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "string-convert": "^0.2.0"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/jssha": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz",
+ "integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/kruptein": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-2.2.3.tgz",
+ "integrity": "sha512-BTwprBPTzkFT9oTugxKd3WnWrX630MqUDsnmBuoa98eQs12oD4n4TeI0GbpdGcYn/73Xueg2rfnw+oK4dovnJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asn1.js": "^5.4.1"
+ },
+ "engines": {
+ "node": ">6"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
+ "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-darwin-arm64": "1.30.1",
+ "lightningcss-darwin-x64": "1.30.1",
+ "lightningcss-freebsd-x64": "1.30.1",
+ "lightningcss-linux-arm-gnueabihf": "1.30.1",
+ "lightningcss-linux-arm64-gnu": "1.30.1",
+ "lightningcss-linux-arm64-musl": "1.30.1",
+ "lightningcss-linux-x64-gnu": "1.30.1",
+ "lightningcss-linux-x64-musl": "1.30.1",
+ "lightningcss-win32-arm64-msvc": "1.30.1",
+ "lightningcss-win32-x64-msvc": "1.30.1"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
+ "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
+ "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
+ "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
+ "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
+ "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
+ "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
+ "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
+ "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
+ "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
+ "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.539.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.539.0.tgz",
+ "integrity": "sha512-VVISr+VF2krO91FeuCrm1rSOLACQUYVy7NQkzrOty52Y8TlTPcXcMdQFj9bYzBgXbWCiywlwSZ3Z8u6a+6bMlg==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
+ "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
+ "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mkdirp": "dist/cjs/src/bin.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/mockjs": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/mockjs/-/mockjs-1.1.0.tgz",
+ "integrity": "sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==",
+ "dev": true,
+ "dependencies": {
+ "commander": "*"
+ },
+ "bin": {
+ "random": "bin/random"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "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.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nodemon": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
+ "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.1.2",
+ "pstree.remy": "^1.1.8",
+ "semver": "^7.5.3",
+ "simple-update-notifier": "^2.0.0",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/nodemon/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/nodemon/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/nodemon/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/on-headers": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/random-bytes": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
+ "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz",
+ "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.7.0",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/raw-body/node_modules/iconv-lite": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
+ "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/rc-cascader": {
+ "version": "3.34.0",
+ "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz",
+ "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.25.7",
+ "classnames": "^2.3.1",
+ "rc-select": "~14.16.2",
+ "rc-tree": "~5.13.0",
+ "rc-util": "^5.43.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-checkbox": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.5.0.tgz",
+ "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.25.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-collapse": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz",
+ "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.3.4",
+ "rc-util": "^5.27.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-dialog": {
+ "version": "9.6.0",
+ "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz",
+ "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/portal": "^1.0.0-8",
+ "classnames": "^2.2.6",
+ "rc-motion": "^2.3.0",
+ "rc-util": "^5.21.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-drawer": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.3.0.tgz",
+ "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "@rc-component/portal": "^1.1.1",
+ "classnames": "^2.2.6",
+ "rc-motion": "^2.6.1",
+ "rc-util": "^5.38.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-dropdown": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz",
+ "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.44.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.11.0",
+ "react-dom": ">=16.11.0"
+ }
+ },
+ "node_modules/rc-field-form": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.0.tgz",
+ "integrity": "sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0",
+ "@rc-component/async-validator": "^5.0.3",
+ "rc-util": "^5.32.2"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-image": {
+ "version": "7.12.0",
+ "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.12.0.tgz",
+ "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.2",
+ "@rc-component/portal": "^1.0.2",
+ "classnames": "^2.2.6",
+ "rc-dialog": "~9.6.0",
+ "rc-motion": "^2.6.2",
+ "rc-util": "^5.34.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-input": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.8.0.tgz",
+ "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.18.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/rc-input-number": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.5.0.tgz",
+ "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/mini-decimal": "^1.0.1",
+ "classnames": "^2.2.5",
+ "rc-input": "~1.8.0",
+ "rc-util": "^5.40.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-mentions": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.20.0.tgz",
+ "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.22.5",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.2.6",
+ "rc-input": "~1.8.0",
+ "rc-menu": "~9.16.0",
+ "rc-textarea": "~1.10.0",
+ "rc-util": "^5.34.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-menu": {
+ "version": "9.16.1",
+ "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz",
+ "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "2.x",
+ "rc-motion": "^2.4.3",
+ "rc-overflow": "^1.3.1",
+ "rc-util": "^5.27.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-motion": {
+ "version": "2.9.5",
+ "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz",
+ "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.44.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-notification": {
+ "version": "5.6.4",
+ "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.4.tgz",
+ "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.9.0",
+ "rc-util": "^5.20.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-overflow": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.4.1.tgz",
+ "integrity": "sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.37.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-pagination": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.1.0.tgz",
+ "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.38.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-picker": {
+ "version": "4.11.3",
+ "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.11.3.tgz",
+ "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.2.1",
+ "rc-overflow": "^1.3.2",
+ "rc-resize-observer": "^1.4.0",
+ "rc-util": "^5.43.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "date-fns": ">= 2.x",
+ "dayjs": ">= 1.x",
+ "luxon": ">= 3.x",
+ "moment": ">= 2.x",
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ },
+ "peerDependenciesMeta": {
+ "date-fns": {
+ "optional": true
+ },
+ "dayjs": {
+ "optional": true
+ },
+ "luxon": {
+ "optional": true
+ },
+ "moment": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/rc-progress": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz",
+ "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.16.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-rate": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.1.tgz",
+ "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.5",
+ "rc-util": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-resize-observer": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz",
+ "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.20.7",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.44.1",
+ "resize-observer-polyfill": "^1.5.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-segmented": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.0.tgz",
+ "integrity": "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-motion": "^2.4.4",
+ "rc-util": "^5.17.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/rc-select": {
+ "version": "14.16.8",
+ "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz",
+ "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/trigger": "^2.1.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.0.1",
+ "rc-overflow": "^1.3.1",
+ "rc-util": "^5.16.1",
+ "rc-virtual-list": "^3.5.2"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/rc-slider": {
+ "version": "11.1.8",
+ "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.8.tgz",
+ "integrity": "sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.5",
+ "rc-util": "^5.36.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-steps": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz",
+ "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.16.7",
+ "classnames": "^2.2.3",
+ "rc-util": "^5.16.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-switch": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz",
+ "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.30.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-table": {
+ "version": "7.51.1",
+ "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.51.1.tgz",
+ "integrity": "sha512-5iq15mTHhvC42TlBLRCoCBLoCmGlbRZAlyF21FonFnS/DIC8DeRqnmdyVREwt2CFbPceM0zSNdEeVfiGaqYsKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/context": "^1.4.0",
+ "classnames": "^2.2.5",
+ "rc-resize-observer": "^1.1.0",
+ "rc-util": "^5.44.3",
+ "rc-virtual-list": "^3.14.2"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-tabs": {
+ "version": "15.7.0",
+ "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.7.0.tgz",
+ "integrity": "sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.2",
+ "classnames": "2.x",
+ "rc-dropdown": "~4.2.0",
+ "rc-menu": "~9.16.0",
+ "rc-motion": "^2.6.2",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.34.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-textarea": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.2.tgz",
+ "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.1",
+ "rc-input": "~1.8.0",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.27.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-tooltip": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.4.0.tgz",
+ "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.2",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.3.1",
+ "rc-util": "^5.44.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-tree": {
+ "version": "5.13.1",
+ "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz",
+ "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.0.1",
+ "rc-util": "^5.16.1",
+ "rc-virtual-list": "^3.5.1"
+ },
+ "engines": {
+ "node": ">=10.x"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/rc-tree-select": {
+ "version": "5.27.0",
+ "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.27.0.tgz",
+ "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.25.7",
+ "classnames": "2.x",
+ "rc-select": "~14.16.2",
+ "rc-tree": "~5.13.0",
+ "rc-util": "^5.43.0"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/rc-upload": {
+ "version": "4.9.2",
+ "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.9.2.tgz",
+ "integrity": "sha512-nHx+9rbd1FKMiMRYsqQ3NkXUv7COHPBo3X1Obwq9SWS6/diF/A0aJ5OHubvwUAIDs+4RMleljV0pcrNUc823GQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "classnames": "^2.2.5",
+ "rc-util": "^5.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-util": {
+ "version": "5.44.4",
+ "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz",
+ "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "react-is": "^18.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-virtual-list": {
+ "version": "3.19.1",
+ "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.19.1.tgz",
+ "integrity": "sha512-DCapO2oyPqmooGhxBuXHM4lFuX+sshQwWqqkuyFA+4rShLe//+GEPVwiDgO+jKtKHtbeYwZoNvetwfHdOf+iUQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.20.0",
+ "classnames": "^2.2.6",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.36.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
+ "node_modules/react-redux": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
+ "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/use-sync-external-store": "^0.0.6",
+ "use-sync-external-store": "^1.4.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.2.25 || ^19",
+ "react": "^18.0 || ^19",
+ "redux": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.0.tgz",
+ "integrity": "sha512-r15M3+LHKgM4SOapNmsH3smAizWds1vJ0Z9C4mWaKnT9/wD7+d/0jYcj6LmOvonkrO4Rgdyp4KQ/29gWN2i1eg==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-smooth": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
+ "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-equals": "^5.0.1",
+ "prop-types": "^15.8.1",
+ "react-transition-group": "^4.4.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/recharts": {
+ "version": "2.15.0",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.0.tgz",
+ "integrity": "sha512-cIvMxDfpAmqAmVgc4yb7pgm/O1tmmkl/CjrvXuW+62/+7jj/iF9Ykm+hb/UJt42TREHMyd3gb+pkgoa2MxgDIw==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.0.0",
+ "eventemitter3": "^4.0.1",
+ "lodash": "^4.17.21",
+ "react-is": "^18.3.1",
+ "react-smooth": "^4.0.0",
+ "recharts-scale": "^0.4.4",
+ "tiny-invariant": "^1.3.1",
+ "victory-vendor": "^36.6.8"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/recharts-scale": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
+ "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
+ "license": "MIT",
+ "dependencies": {
+ "decimal.js-light": "^2.4.1"
+ }
+ },
+ "node_modules/redux": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+ "license": "MIT"
+ },
+ "node_modules/redux-thunk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+ "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "redux": "^5.0.0"
+ }
+ },
+ "node_modules/reselect": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+ "license": "MIT"
+ },
+ "node_modules/resize-observer-polyfill": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==",
+ "license": "MIT"
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz",
+ "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.46.2",
+ "@rollup/rollup-android-arm64": "4.46.2",
+ "@rollup/rollup-darwin-arm64": "4.46.2",
+ "@rollup/rollup-darwin-x64": "4.46.2",
+ "@rollup/rollup-freebsd-arm64": "4.46.2",
+ "@rollup/rollup-freebsd-x64": "4.46.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.46.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.46.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.46.2",
+ "@rollup/rollup-linux-arm64-musl": "4.46.2",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.46.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.46.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.46.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.46.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.46.2",
+ "@rollup/rollup-linux-x64-gnu": "4.46.2",
+ "@rollup/rollup-linux-x64-musl": "4.46.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.46.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.46.2",
+ "@rollup/rollup-win32-x64-msvc": "4.46.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/scroll-into-view-if-needed": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
+ "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "compute-scroll-into-view": "^3.0.2"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.5",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "mime-types": "^3.0.1",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/session-file-store": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/session-file-store/-/session-file-store-1.5.0.tgz",
+ "integrity": "sha512-60IZaJNzyu2tIeHutkYE8RiXVx3KRvacOxfLr2Mj92SIsRIroDsH0IlUUR6fJAjoTW4RQISbaOApa2IZpIwFdQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bagpipe": "^0.3.5",
+ "fs-extra": "^8.0.1",
+ "kruptein": "^2.0.4",
+ "object-assign": "^4.1.1",
+ "retry": "^0.12.0",
+ "write-file-atomic": "3.0.3"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/session-file-store/node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/session-file-store/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "license": "MIT",
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/session-file-store/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/simple-update-notifier/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/string-convert": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
+ "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==",
+ "license": "MIT"
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/stylis": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
+ "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
+ "license": "MIT"
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz",
+ "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
+ "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tar": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
+ "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@isaacs/fs-minipass": "^4.0.0",
+ "chownr": "^3.0.0",
+ "minipass": "^7.1.2",
+ "minizlib": "^3.0.1",
+ "mkdirp": "^3.0.1",
+ "yallist": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tar/node_modules/yallist": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/throttle-debounce": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
+ "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.22"
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.14",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toggle-selection": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
+ "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
+ "license": "MIT"
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/touch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+ "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typedarray-to-buffer": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
+ "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-typedarray": "^1.0.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.39.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.1.tgz",
+ "integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.39.1",
+ "@typescript-eslint/parser": "8.39.1",
+ "@typescript-eslint/typescript-estree": "8.39.1",
+ "@typescript-eslint/utils": "8.39.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/uid-safe": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
+ "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "random-bytes": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "7.10.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
+ "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
+ "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/victory-vendor": {
+ "version": "36.9.2",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
+ "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
+ "license": "MIT AND ISC",
+ "dependencies": {
+ "@types/d3-array": "^3.0.3",
+ "@types/d3-ease": "^3.0.0",
+ "@types/d3-interpolate": "^3.0.1",
+ "@types/d3-scale": "^4.0.2",
+ "@types/d3-shape": "^3.1.0",
+ "@types/d3-time": "^3.0.0",
+ "@types/d3-timer": "^3.0.0",
+ "d3-array": "^3.1.6",
+ "d3-ease": "^3.0.1",
+ "d3-interpolate": "^3.0.1",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.1.0",
+ "d3-time": "^3.0.0",
+ "d3-timer": "^3.0.1"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz",
+ "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.6",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.14"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/fdir": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/write-file-atomic": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
+ "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "is-typedarray": "^1.0.0",
+ "signal-exit": "^3.0.2",
+ "typedarray-to-buffer": "^3.1.5"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "4.5.7",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
+ "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.2.2"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
index 99f07ac33..5d44f3bae 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,51 +1,51 @@
-{
- "name": "edatamate",
- "private": true,
- "version": "0.0.0",
- "type": "module",
- "scripts": {
- "dev": "vite",
- "mock": "cd src/mock && nodemon --config nodemon.json --inspect=0.0.0.0:9229 mock.cjs --env=development --port=8002",
- "build": "vite build",
- "lint": "eslint .",
- "preview": "vite preview"
- },
- "dependencies": {
- "@reduxjs/toolkit": "^2.11.0",
- "@xyflow/react": "^12.8.3",
- "antd": "^5.27.0",
- "jssha": "^3.3.1",
- "lucide-react": "^0.539.0",
- "react": "^18.1.1",
- "react-dom": "^18.1.1",
- "react-redux": "^9.2.0",
- "react-router": "^7.8.0",
- "recharts": "2.15.0"
- },
- "devDependencies": {
- "@eslint/js": "^9.33.0",
- "@tailwindcss/vite": "^4.1.12",
- "@types/node": "^24.2.1",
- "@types/react": "^18.1.10",
- "@types/react-dom": "^18.1.7",
- "@vitejs/plugin-react": "^5.0.0",
- "body-parser": "^2.2.0",
- "eslint": "^9.33.0",
- "eslint-plugin-react-hooks": "^5.2.0",
- "eslint-plugin-react-refresh": "^0.4.20",
- "express": "^5.1.0",
- "express-session": "^1.18.2",
- "fs-extra": "^11.3.1",
- "globals": "^16.3.0",
- "lodash": "^4.17.21",
- "minimist": "^1.2.8",
- "mockjs": "^1.1.0",
- "nodemon": "^3.1.10",
- "postcss": "^8.5.6",
- "session-file-store": "^1.5.0",
- "tailwindcss": "^4.1.12",
- "typescript": "~5.8.3",
- "typescript-eslint": "^8.39.1",
- "vite": "^7.1.2"
- }
-}
+{
+ "name": "edatamate",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "mock": "cd src/mock && nodemon --config nodemon.json --inspect=0.0.0.0:9229 mock.cjs --env=development --port=8002",
+ "build": "vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@reduxjs/toolkit": "^2.11.0",
+ "@xyflow/react": "^12.8.3",
+ "antd": "^5.27.0",
+ "jssha": "^3.3.1",
+ "lucide-react": "^0.539.0",
+ "react": "^18.1.1",
+ "react-dom": "^18.1.1",
+ "react-redux": "^9.2.0",
+ "react-router": "^7.8.0",
+ "recharts": "2.15.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.33.0",
+ "@tailwindcss/vite": "^4.1.12",
+ "@types/node": "^24.2.1",
+ "@types/react": "^18.1.10",
+ "@types/react-dom": "^18.1.7",
+ "@vitejs/plugin-react": "^5.0.0",
+ "body-parser": "^2.2.0",
+ "eslint": "^9.33.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.20",
+ "express": "^5.1.0",
+ "express-session": "^1.18.2",
+ "fs-extra": "^11.3.1",
+ "globals": "^16.3.0",
+ "lodash": "^4.17.21",
+ "minimist": "^1.2.8",
+ "mockjs": "^1.1.0",
+ "nodemon": "^3.1.10",
+ "postcss": "^8.5.6",
+ "session-file-store": "^1.5.0",
+ "tailwindcss": "^4.1.12",
+ "typescript": "~5.8.3",
+ "typescript-eslint": "^8.39.1",
+ "vite": "^7.1.2"
+ }
+}
diff --git a/frontend/src/components/ActionDropdown.tsx b/frontend/src/components/ActionDropdown.tsx
index d6d7ab48e..9fe696bf4 100644
--- a/frontend/src/components/ActionDropdown.tsx
+++ b/frontend/src/components/ActionDropdown.tsx
@@ -1,117 +1,117 @@
-import { Dropdown, Popconfirm, Button, Space } from "antd";
-import { EllipsisOutlined } from "@ant-design/icons";
-import { useState } from "react";
-
-interface ActionItem {
- key: string;
- label: string;
- icon?: React.ReactNode;
- danger?: boolean;
- confirm?: {
- title: string;
- description?: string;
- okText?: string;
- cancelText?: string;
- };
-}
-
-interface ActionDropdownProps {
- actions?: ActionItem[];
- onAction?: (key: string, action: ActionItem) => void;
- placement?:
- | "bottomRight"
- | "topLeft"
- | "topCenter"
- | "topRight"
- | "bottomLeft"
- | "bottomCenter"
- | "top"
- | "bottom";
-}
-
-const ActionDropdown = ({
- actions = [],
- onAction,
- placement = "bottomRight",
-}: ActionDropdownProps) => {
- const [open, setOpen] = useState(false);
- const handleActionClick = (action: ActionItem) => {
- if (action.confirm) {
- // 如果有确认框,不立即执行,等待确认
- return;
- }
- // 执行操作
- onAction?.(action.key, action);
- // 如果没有确认框,则立即关闭 Dropdown
- setOpen(false);
- };
-
- const dropdownContent = (
-
-
- {actions.map((action) => {
- if (action.confirm) {
- return (
- {
- onAction?.(action.key, action);
- setOpen(false);
- }}
- okText={action.confirm.okText || "确定"}
- cancelText={action.confirm.cancelText || "取消"}
- okType={action.danger ? "danger" : "primary"}
- styles={{ root: { zIndex: 9999 } }}
- >
-
-
- );
- }
-
- return (
-
- );
- })}
-
-
- );
-
- return (
-
- }
- />
-
- );
-};
-
-export default ActionDropdown;
+import { Dropdown, Popconfirm, Button, Space } from "antd";
+import { EllipsisOutlined } from "@ant-design/icons";
+import { useState } from "react";
+
+interface ActionItem {
+ key: string;
+ label: string;
+ icon?: React.ReactNode;
+ danger?: boolean;
+ confirm?: {
+ title: string;
+ description?: string;
+ okText?: string;
+ cancelText?: string;
+ };
+}
+
+interface ActionDropdownProps {
+ actions?: ActionItem[];
+ onAction?: (key: string, action: ActionItem) => void;
+ placement?:
+ | "bottomRight"
+ | "topLeft"
+ | "topCenter"
+ | "topRight"
+ | "bottomLeft"
+ | "bottomCenter"
+ | "top"
+ | "bottom";
+}
+
+const ActionDropdown = ({
+ actions = [],
+ onAction,
+ placement = "bottomRight",
+}: ActionDropdownProps) => {
+ const [open, setOpen] = useState(false);
+ const handleActionClick = (action: ActionItem) => {
+ if (action.confirm) {
+ // 如果有确认框,不立即执行,等待确认
+ return;
+ }
+ // 执行操作
+ onAction?.(action.key, action);
+ // 如果没有确认框,则立即关闭 Dropdown
+ setOpen(false);
+ };
+
+ const dropdownContent = (
+
+
+ {actions.map((action) => {
+ if (action.confirm) {
+ return (
+ {
+ onAction?.(action.key, action);
+ setOpen(false);
+ }}
+ okText={action.confirm.okText || "确定"}
+ cancelText={action.confirm.cancelText || "取消"}
+ okType={action.danger ? "danger" : "primary"}
+ styles={{ root: { zIndex: 9999 } }}
+ >
+
+
+ );
+ }
+
+ return (
+
+ );
+ })}
+
+
+ );
+
+ return (
+
+ }
+ />
+
+ );
+};
+
+export default ActionDropdown;
diff --git a/frontend/src/components/AddTagPopover.tsx b/frontend/src/components/AddTagPopover.tsx
index f4118dfca..92d53047f 100644
--- a/frontend/src/components/AddTagPopover.tsx
+++ b/frontend/src/components/AddTagPopover.tsx
@@ -1,134 +1,134 @@
-import { Button, Input, Popover, theme, Tag, Empty } from "antd";
-import { PlusOutlined } from "@ant-design/icons";
-import { useEffect, useMemo, useState } from "react";
-
-interface Tag {
- id: number;
- name: string;
- color: string;
-}
-
-interface AddTagPopoverProps {
- tags: Tag[];
- onFetchTags?: () => Promise;
- onAddTag?: (tag: Tag) => void;
- onCreateAndTag?: (tagName: string) => void;
-}
-
-export default function AddTagPopover({
- tags,
- onFetchTags,
- onAddTag,
- onCreateAndTag,
-}: AddTagPopoverProps) {
- const { token } = theme.useToken();
- const [showPopover, setShowPopover] = useState(false);
-
- const [newTag, setNewTag] = useState("");
- const [allTags, setAllTags] = useState([]);
-
- const tagsSet = useMemo(() => new Set(tags.map((tag) => tag.id)), [tags]);
-
- const fetchTags = async () => {
- if (onFetchTags && showPopover) {
- const data = await onFetchTags?.();
- setAllTags(data || []);
- }
- };
- useEffect(() => {
- fetchTags();
- }, [showPopover]);
-
- const availableTags = useMemo(() => {
- return allTags.filter((tag) => !tagsSet.has(tag.id));
- }, [allTags, tagsSet]);
-
- const handleCreateAndAddTag = () => {
- if (newTag.trim()) {
- onCreateAndTag?.(newTag.trim());
- setNewTag("");
- }
-
- setShowPopover(false);
- };
-
- const tagPlusStyle: React.CSSProperties = {
- height: 22,
- background: token.colorBgContainer,
- borderStyle: "dashed",
- };
-
- return (
- <>
-
-
- 添加标签
-
- {/* Available Tags */}
- {availableTags?.length ? (
-
-
选择现有标签
-
- {availableTags.map((tag) => (
-
{
- onAddTag?.(tag.name);
- setShowPopover(false);
- }}
- >
-
- {tag.name}
-
- ))}
-
-
- ) : (
-
- )}
-
- {/* Create New Tag */}
-
-
创建新标签
-
- setNewTag(e.target.value)}
- className="h-8 text-sm"
- />
-
-
-
-
-
-
- }
- >
- }
- className="cursor-pointer"
- onClick={() => setShowPopover(true)}
- >
- 添加标签
-
-
- >
- );
-}
+import { Button, Input, Popover, theme, Tag, Empty } from "antd";
+import { PlusOutlined } from "@ant-design/icons";
+import { useEffect, useMemo, useState } from "react";
+
+interface Tag {
+ id: number;
+ name: string;
+ color: string;
+}
+
+interface AddTagPopoverProps {
+ tags: Tag[];
+ onFetchTags?: () => Promise;
+ onAddTag?: (tag: Tag) => void;
+ onCreateAndTag?: (tagName: string) => void;
+}
+
+export default function AddTagPopover({
+ tags,
+ onFetchTags,
+ onAddTag,
+ onCreateAndTag,
+}: AddTagPopoverProps) {
+ const { token } = theme.useToken();
+ const [showPopover, setShowPopover] = useState(false);
+
+ const [newTag, setNewTag] = useState("");
+ const [allTags, setAllTags] = useState([]);
+
+ const tagsSet = useMemo(() => new Set(tags.map((tag) => tag.id)), [tags]);
+
+ const fetchTags = async () => {
+ if (onFetchTags && showPopover) {
+ const data = await onFetchTags?.();
+ setAllTags(data || []);
+ }
+ };
+ useEffect(() => {
+ fetchTags();
+ }, [showPopover]);
+
+ const availableTags = useMemo(() => {
+ return allTags.filter((tag) => !tagsSet.has(tag.id));
+ }, [allTags, tagsSet]);
+
+ const handleCreateAndAddTag = () => {
+ if (newTag.trim()) {
+ onCreateAndTag?.(newTag.trim());
+ setNewTag("");
+ }
+
+ setShowPopover(false);
+ };
+
+ const tagPlusStyle: React.CSSProperties = {
+ height: 22,
+ background: token.colorBgContainer,
+ borderStyle: "dashed",
+ };
+
+ return (
+ <>
+
+
+ 添加标签
+
+ {/* Available Tags */}
+ {availableTags?.length ? (
+
+
选择现有标签
+
+ {availableTags.map((tag) => (
+
{
+ onAddTag?.(tag.name);
+ setShowPopover(false);
+ }}
+ >
+
+ {tag.name}
+
+ ))}
+
+
+ ) : (
+
+ )}
+
+ {/* Create New Tag */}
+
+
创建新标签
+
+ setNewTag(e.target.value)}
+ className="h-8 text-sm"
+ />
+
+
+
+
+
+
+ }
+ >
+ }
+ className="cursor-pointer"
+ onClick={() => setShowPopover(true)}
+ >
+ 添加标签
+
+
+ >
+ );
+}
diff --git a/frontend/src/components/AdvancedCronScheduler.tsx b/frontend/src/components/AdvancedCronScheduler.tsx
index b700615f2..8c331d335 100644
--- a/frontend/src/components/AdvancedCronScheduler.tsx
+++ b/frontend/src/components/AdvancedCronScheduler.tsx
@@ -1,556 +1,556 @@
-import React, { useState, useCallback, useEffect } from "react";
-import {
- Card,
- Select,
- Input,
- Space,
- Typography,
- Row,
- Col,
- Divider,
- Button,
- Tooltip,
-} from "antd";
-import { InfoCircleOutlined } from "@ant-design/icons";
-
-const { Title, Text } = Typography;
-const { Option } = Select;
-
-export interface AdvancedCronConfig {
- second: string;
- minute: string;
- hour: string;
- day: string;
- month: string;
- weekday: string;
- year?: string;
- cronExpression: string;
-}
-
-interface AdvancedCronSchedulerProps {
- value?: AdvancedCronConfig;
- onChange?: (config: AdvancedCronConfig) => void;
- showYear?: boolean; // 是否显示年份字段
- className?: string;
-}
-
-// Cron字段的选项配置
-const CRON_OPTIONS = {
- second: {
- label: "秒",
- range: [0, 59],
- examples: ["0", "*/5", "10,20,30", "0-30"],
- description: "秒钟 (0-59)",
- },
- minute: {
- label: "分钟",
- range: [0, 59],
- examples: ["0", "*/15", "5,10,15", "0-30"],
- description: "分钟 (0-59)",
- },
- hour: {
- label: "小时",
- range: [0, 23],
- examples: ["0", "*/2", "8,14,20", "9-17"],
- description: "小时 (0-23)",
- },
- day: {
- label: "日",
- range: [1, 31],
- examples: ["*", "1", "1,15", "1-15", "*/2"],
- description: "日期 (1-31)",
- },
- month: {
- label: "月",
- range: [1, 12],
- examples: ["*", "1", "1,6,12", "3-9", "*/3"],
- description: "月份 (1-12)",
- },
- year: {
- label: "年",
- range: [1970, 2099],
- examples: ["*", "2024", "2024-2026", "*/2"],
- description: "年份 (1970-2099)",
- },
- weekday: {
- label: "周",
- range: [0, 7], // 0和7都表示周日
- examples: ["*", "1", "1-5", "1,3,5", "0,6"],
- description: "星期 (0-7, 0和7都表示周日)",
- weekNames: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
- },
-};
-
-// 生成常用的cron表达式选项
-const generateCommonOptions = (field: keyof typeof CRON_OPTIONS) => {
- const options = [
- { label: "* (任意)", value: "*" },
- { label: "? (不指定)", value: "?" }, // 仅用于日和周字段
- ];
-
- const config = CRON_OPTIONS[field];
- const [start, end] = config.range;
-
- // 添加具体数值选项
- if (field === "weekday") {
- const weekdayConfig = config as { weekNames?: string[] };
- weekdayConfig.weekNames?.forEach((name: string, index: number) => {
- options.push({ label: `${index} (${name})`, value: index.toString() });
- });
- // 添加7作为周日的别名
- options.push({ label: "7 (周日)", value: "7" });
- } else {
- // 添加部分具体数值
- const step =
- field === "year" ? 5 : field === "day" || field === "month" ? 3 : 5;
- for (let i = start; i <= end; i += step) {
- if (i <= end) {
- options.push({ label: i.toString(), value: i.toString() });
- }
- }
- }
-
- // 添加间隔选项
- if (field !== "year") {
- options.push(
- { label: "*/2 (每2个)", value: "*/2" },
- { label: "*/5 (每5个)", value: "*/5" },
- { label: "*/10 (每10个)", value: "*/10" }
- );
- }
-
- // 添加范围选项
- if (field === "hour") {
- options.push(
- { label: "9-17 (工作时间)", value: "9-17" },
- { label: "0-6 (凌晨)", value: "0-6" }
- );
- } else if (field === "weekday") {
- options.push(
- { label: "1-5 (工作日)", value: "1-5" },
- { label: "0,6 (周末)", value: "0,6" }
- );
- } else if (field === "day") {
- options.push(
- { label: "1-15 (上半月)", value: "1-15" },
- { label: "16-31 (下半月)", value: "16-31" }
- );
- }
-
- return options;
-};
-
-// 验证cron字段值
-const validateCronField = (
- value: string,
- field: keyof typeof CRON_OPTIONS
-): boolean => {
- if (!value || value === "*" || value === "?") return true;
-
- const config = CRON_OPTIONS[field];
- const [min, max] = config.range;
-
- // 验证基本格式
- const patterns = [
- /^\d+$/, // 单个数字
- /^\d+-\d+$/, // 范围
- /^\*\/\d+$/, // 间隔
- /^(\d+,)*\d+$/, // 列表
- /^(\d+-\d+,)*(\d+-\d+|\d+)$/, // 复合表达式
- ];
-
- if (!patterns.some((pattern) => pattern.test(value))) {
- return false;
- }
-
- // 验证数值范围
- const numbers = value.match(/\d+/g);
- if (numbers) {
- return numbers.every((num) => {
- const n = parseInt(num);
- return n >= min && n <= max;
- });
- }
-
- return true;
-};
-
-// 生成cron表达式
-const generateCronExpression = (
- config: Omit
-): string => {
- const { second, minute, hour, day, month, weekday, year } = config;
-
- const parts = [second, minute, hour, day, month, weekday];
- if (year && year !== "*") {
- parts.push(year);
- }
-
- return parts.join(" ");
-};
-
-// 解析cron表达式为人类可读的描述
-const parseCronDescription = (cronExpression: string): string => {
- const parts = cronExpression.split(" ");
- if (parts.length < 6) return cronExpression;
-
- const [second, minute, hour, day, month, weekday, year] = parts;
-
- const descriptions = [];
-
- // 时间描述
- if (second === "0" && minute !== "*" && hour !== "*") {
- descriptions.push(`${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`);
- } else {
- if (hour !== "*") descriptions.push(`${hour}时`);
- if (minute !== "*") descriptions.push(`${minute}分`);
- if (second !== "*" && second !== "0") descriptions.push(`${second}秒`);
- }
-
- // 日期描述
- if (day !== "*" && day !== "?") {
- descriptions.push(`${day}日`);
- }
-
- // 月份描述
- if (month !== "*") {
- descriptions.push(`${month}月`);
- }
-
- // 星期描述
- if (weekday !== "*" && weekday !== "?") {
- const weekNames = [
- "周日",
- "周一",
- "周二",
- "周三",
- "周四",
- "周五",
- "周六",
- "周日",
- ];
- if (weekday === "1-5") {
- descriptions.push("工作日");
- } else if (weekday === "0,6") {
- descriptions.push("周末");
- } else if (/^\d$/.test(weekday)) {
- descriptions.push(weekNames[parseInt(weekday)]);
- } else {
- descriptions.push(`周${weekday}`);
- }
- }
-
- // 年份描述
- if (year && year !== "*") {
- descriptions.push(`${year}年`);
- }
-
- return descriptions.length > 0 ? descriptions.join(" ") : "每秒执行";
-};
-
-const AdvancedCronScheduler: React.FC = ({
- value = {
- second: "0",
- minute: "0",
- hour: "0",
- day: "*",
- month: "*",
- weekday: "?",
- year: "*",
- cronExpression: "0 0 0 * * ?",
- },
- onChange,
- showYear = false,
- className,
-}) => {
- const [config, setConfig] = useState(value);
- const [customMode, setCustomMode] = useState(false);
-
- // 更新配置
- const updateConfig = useCallback(
- (updates: Partial) => {
- const newConfig = { ...config, ...updates };
- newConfig.cronExpression = generateCronExpression(newConfig);
- setConfig(newConfig);
- onChange?.(newConfig);
- },
- [config, onChange]
- );
-
- // 同步外部值
- useEffect(() => {
- setConfig(value);
- }, [value]);
-
- // 处理字段变化
- const handleFieldChange = (
- field: keyof AdvancedCronConfig,
- fieldValue: string
- ) => {
- if (field === "cronExpression") {
- // 直接编辑cron表达式
- const parts = fieldValue.split(" ");
- if (parts.length >= 6) {
- const newConfig = {
- second: parts[0] || "0",
- minute: parts[1] || "0",
- hour: parts[2] || "0",
- day: parts[3] || "*",
- month: parts[4] || "*",
- weekday: parts[5] || "?",
- year: parts[6] || "*",
- cronExpression: fieldValue,
- };
- setConfig(newConfig);
- onChange?.(newConfig);
- }
- } else {
- updateConfig({ [field]: fieldValue });
- }
- };
-
- // 快速设置预设
- const setPreset = (preset: Partial) => {
- updateConfig(preset);
- };
-
- // 常用预设
- const commonPresets = [
- {
- label: "每秒",
- config: {
- second: "*",
- minute: "*",
- hour: "*",
- day: "*",
- month: "*",
- weekday: "?",
- },
- },
- {
- label: "每分钟",
- config: {
- second: "0",
- minute: "*",
- hour: "*",
- day: "*",
- month: "*",
- weekday: "?",
- },
- },
- {
- label: "每小时",
- config: {
- second: "0",
- minute: "0",
- hour: "*",
- day: "*",
- month: "*",
- weekday: "?",
- },
- },
- {
- label: "每天午夜",
- config: {
- second: "0",
- minute: "0",
- hour: "0",
- day: "*",
- month: "*",
- weekday: "?",
- },
- },
- {
- label: "每周一9点",
- config: {
- second: "0",
- minute: "0",
- hour: "9",
- day: "?",
- month: "*",
- weekday: "1",
- },
- },
- {
- label: "每月1日0点",
- config: {
- second: "0",
- minute: "0",
- hour: "0",
- day: "1",
- month: "*",
- weekday: "?",
- },
- },
- {
- label: "工作日9点",
- config: {
- second: "0",
- minute: "0",
- hour: "9",
- day: "?",
- month: "*",
- weekday: "1-5",
- },
- },
- {
- label: "每15分钟",
- config: {
- second: "0",
- minute: "*/15",
- hour: "*",
- day: "*",
- month: "*",
- weekday: "?",
- },
- },
- ];
-
- const fields: Array = [
- "second",
- "minute",
- "hour",
- "day",
- "month",
- "weekday",
- ];
- if (showYear) fields.push("year");
-
- return (
-
-
- {/* 标题和切换模式 */}
-
-
- 高级 Cron 表达式配置
-
-
-
-
- {/* 快速预设 */}
-
-
快速预设:
-
- {commonPresets.map((preset, index) => (
-
- ))}
-
-
-
- {customMode ? (
- /* 手动编辑模式 */
-
- Cron 表达式:
-
- handleFieldChange("cronExpression", e.target.value)
- }
- placeholder="秒 分 时 日 月 周 [年]"
- />
-
- 格式:秒(0-59) 分(0-59) 时(0-23) 日(1-31) 月(1-12) 周(0-7)
- [年(1970-2099)]
-
-
- ) : (
- /* 向导模式 */
-
- {fields.map((field) => {
- const fieldConfig = CRON_OPTIONS[field];
- const options = generateCommonOptions(field);
- return (
-
-
-
- {fieldConfig.label}
-
-
-
-
-
- {/* 自定义输入 */}
-
handleFieldChange(field, e.target.value)}
- status={
- validateCronField(config[field] || "", field) ? "" : "error"
- }
- />
-
-
- );
- })}
-
- )}
-
-
-
- {/* 结果预览 */}
-
- 生成的 Cron 表达式:
-
-
- 描述:{parseCronDescription(config.cronExpression)}
-
-
-
- {/* 字段说明 */}
-
-
字段说明:
-
-
- • * 表示任意值
- • ? 表示不指定值(仅日、周字段)
- • */5 表示每5个单位
- • 1-5 表示范围
- • 1,3,5 表示列表
- • 日和周字段不能同时指定具体值
-
-
-
-
-
- );
-};
-
-export default AdvancedCronScheduler;
+import React, { useState, useCallback, useEffect } from "react";
+import {
+ Card,
+ Select,
+ Input,
+ Space,
+ Typography,
+ Row,
+ Col,
+ Divider,
+ Button,
+ Tooltip,
+} from "antd";
+import { InfoCircleOutlined } from "@ant-design/icons";
+
+const { Title, Text } = Typography;
+const { Option } = Select;
+
+export interface AdvancedCronConfig {
+ second: string;
+ minute: string;
+ hour: string;
+ day: string;
+ month: string;
+ weekday: string;
+ year?: string;
+ cronExpression: string;
+}
+
+interface AdvancedCronSchedulerProps {
+ value?: AdvancedCronConfig;
+ onChange?: (config: AdvancedCronConfig) => void;
+ showYear?: boolean; // 是否显示年份字段
+ className?: string;
+}
+
+// Cron字段的选项配置
+const CRON_OPTIONS = {
+ second: {
+ label: "秒",
+ range: [0, 59],
+ examples: ["0", "*/5", "10,20,30", "0-30"],
+ description: "秒钟 (0-59)",
+ },
+ minute: {
+ label: "分钟",
+ range: [0, 59],
+ examples: ["0", "*/15", "5,10,15", "0-30"],
+ description: "分钟 (0-59)",
+ },
+ hour: {
+ label: "小时",
+ range: [0, 23],
+ examples: ["0", "*/2", "8,14,20", "9-17"],
+ description: "小时 (0-23)",
+ },
+ day: {
+ label: "日",
+ range: [1, 31],
+ examples: ["*", "1", "1,15", "1-15", "*/2"],
+ description: "日期 (1-31)",
+ },
+ month: {
+ label: "月",
+ range: [1, 12],
+ examples: ["*", "1", "1,6,12", "3-9", "*/3"],
+ description: "月份 (1-12)",
+ },
+ year: {
+ label: "年",
+ range: [1970, 2099],
+ examples: ["*", "2024", "2024-2026", "*/2"],
+ description: "年份 (1970-2099)",
+ },
+ weekday: {
+ label: "周",
+ range: [0, 7], // 0和7都表示周日
+ examples: ["*", "1", "1-5", "1,3,5", "0,6"],
+ description: "星期 (0-7, 0和7都表示周日)",
+ weekNames: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
+ },
+};
+
+// 生成常用的cron表达式选项
+const generateCommonOptions = (field: keyof typeof CRON_OPTIONS) => {
+ const options = [
+ { label: "* (任意)", value: "*" },
+ { label: "? (不指定)", value: "?" }, // 仅用于日和周字段
+ ];
+
+ const config = CRON_OPTIONS[field];
+ const [start, end] = config.range;
+
+ // 添加具体数值选项
+ if (field === "weekday") {
+ const weekdayConfig = config as { weekNames?: string[] };
+ weekdayConfig.weekNames?.forEach((name: string, index: number) => {
+ options.push({ label: `${index} (${name})`, value: index.toString() });
+ });
+ // 添加7作为周日的别名
+ options.push({ label: "7 (周日)", value: "7" });
+ } else {
+ // 添加部分具体数值
+ const step =
+ field === "year" ? 5 : field === "day" || field === "month" ? 3 : 5;
+ for (let i = start; i <= end; i += step) {
+ if (i <= end) {
+ options.push({ label: i.toString(), value: i.toString() });
+ }
+ }
+ }
+
+ // 添加间隔选项
+ if (field !== "year") {
+ options.push(
+ { label: "*/2 (每2个)", value: "*/2" },
+ { label: "*/5 (每5个)", value: "*/5" },
+ { label: "*/10 (每10个)", value: "*/10" }
+ );
+ }
+
+ // 添加范围选项
+ if (field === "hour") {
+ options.push(
+ { label: "9-17 (工作时间)", value: "9-17" },
+ { label: "0-6 (凌晨)", value: "0-6" }
+ );
+ } else if (field === "weekday") {
+ options.push(
+ { label: "1-5 (工作日)", value: "1-5" },
+ { label: "0,6 (周末)", value: "0,6" }
+ );
+ } else if (field === "day") {
+ options.push(
+ { label: "1-15 (上半月)", value: "1-15" },
+ { label: "16-31 (下半月)", value: "16-31" }
+ );
+ }
+
+ return options;
+};
+
+// 验证cron字段值
+const validateCronField = (
+ value: string,
+ field: keyof typeof CRON_OPTIONS
+): boolean => {
+ if (!value || value === "*" || value === "?") return true;
+
+ const config = CRON_OPTIONS[field];
+ const [min, max] = config.range;
+
+ // 验证基本格式
+ const patterns = [
+ /^\d+$/, // 单个数字
+ /^\d+-\d+$/, // 范围
+ /^\*\/\d+$/, // 间隔
+ /^(\d+,)*\d+$/, // 列表
+ /^(\d+-\d+,)*(\d+-\d+|\d+)$/, // 复合表达式
+ ];
+
+ if (!patterns.some((pattern) => pattern.test(value))) {
+ return false;
+ }
+
+ // 验证数值范围
+ const numbers = value.match(/\d+/g);
+ if (numbers) {
+ return numbers.every((num) => {
+ const n = parseInt(num);
+ return n >= min && n <= max;
+ });
+ }
+
+ return true;
+};
+
+// 生成cron表达式
+const generateCronExpression = (
+ config: Omit
+): string => {
+ const { second, minute, hour, day, month, weekday, year } = config;
+
+ const parts = [second, minute, hour, day, month, weekday];
+ if (year && year !== "*") {
+ parts.push(year);
+ }
+
+ return parts.join(" ");
+};
+
+// 解析cron表达式为人类可读的描述
+const parseCronDescription = (cronExpression: string): string => {
+ const parts = cronExpression.split(" ");
+ if (parts.length < 6) return cronExpression;
+
+ const [second, minute, hour, day, month, weekday, year] = parts;
+
+ const descriptions = [];
+
+ // 时间描述
+ if (second === "0" && minute !== "*" && hour !== "*") {
+ descriptions.push(`${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`);
+ } else {
+ if (hour !== "*") descriptions.push(`${hour}时`);
+ if (minute !== "*") descriptions.push(`${minute}分`);
+ if (second !== "*" && second !== "0") descriptions.push(`${second}秒`);
+ }
+
+ // 日期描述
+ if (day !== "*" && day !== "?") {
+ descriptions.push(`${day}日`);
+ }
+
+ // 月份描述
+ if (month !== "*") {
+ descriptions.push(`${month}月`);
+ }
+
+ // 星期描述
+ if (weekday !== "*" && weekday !== "?") {
+ const weekNames = [
+ "周日",
+ "周一",
+ "周二",
+ "周三",
+ "周四",
+ "周五",
+ "周六",
+ "周日",
+ ];
+ if (weekday === "1-5") {
+ descriptions.push("工作日");
+ } else if (weekday === "0,6") {
+ descriptions.push("周末");
+ } else if (/^\d$/.test(weekday)) {
+ descriptions.push(weekNames[parseInt(weekday)]);
+ } else {
+ descriptions.push(`周${weekday}`);
+ }
+ }
+
+ // 年份描述
+ if (year && year !== "*") {
+ descriptions.push(`${year}年`);
+ }
+
+ return descriptions.length > 0 ? descriptions.join(" ") : "每秒执行";
+};
+
+const AdvancedCronScheduler: React.FC = ({
+ value = {
+ second: "0",
+ minute: "0",
+ hour: "0",
+ day: "*",
+ month: "*",
+ weekday: "?",
+ year: "*",
+ cronExpression: "0 0 0 * * ?",
+ },
+ onChange,
+ showYear = false,
+ className,
+}) => {
+ const [config, setConfig] = useState(value);
+ const [customMode, setCustomMode] = useState(false);
+
+ // 更新配置
+ const updateConfig = useCallback(
+ (updates: Partial) => {
+ const newConfig = { ...config, ...updates };
+ newConfig.cronExpression = generateCronExpression(newConfig);
+ setConfig(newConfig);
+ onChange?.(newConfig);
+ },
+ [config, onChange]
+ );
+
+ // 同步外部值
+ useEffect(() => {
+ setConfig(value);
+ }, [value]);
+
+ // 处理字段变化
+ const handleFieldChange = (
+ field: keyof AdvancedCronConfig,
+ fieldValue: string
+ ) => {
+ if (field === "cronExpression") {
+ // 直接编辑cron表达式
+ const parts = fieldValue.split(" ");
+ if (parts.length >= 6) {
+ const newConfig = {
+ second: parts[0] || "0",
+ minute: parts[1] || "0",
+ hour: parts[2] || "0",
+ day: parts[3] || "*",
+ month: parts[4] || "*",
+ weekday: parts[5] || "?",
+ year: parts[6] || "*",
+ cronExpression: fieldValue,
+ };
+ setConfig(newConfig);
+ onChange?.(newConfig);
+ }
+ } else {
+ updateConfig({ [field]: fieldValue });
+ }
+ };
+
+ // 快速设置预设
+ const setPreset = (preset: Partial) => {
+ updateConfig(preset);
+ };
+
+ // 常用预设
+ const commonPresets = [
+ {
+ label: "每秒",
+ config: {
+ second: "*",
+ minute: "*",
+ hour: "*",
+ day: "*",
+ month: "*",
+ weekday: "?",
+ },
+ },
+ {
+ label: "每分钟",
+ config: {
+ second: "0",
+ minute: "*",
+ hour: "*",
+ day: "*",
+ month: "*",
+ weekday: "?",
+ },
+ },
+ {
+ label: "每小时",
+ config: {
+ second: "0",
+ minute: "0",
+ hour: "*",
+ day: "*",
+ month: "*",
+ weekday: "?",
+ },
+ },
+ {
+ label: "每天午夜",
+ config: {
+ second: "0",
+ minute: "0",
+ hour: "0",
+ day: "*",
+ month: "*",
+ weekday: "?",
+ },
+ },
+ {
+ label: "每周一9点",
+ config: {
+ second: "0",
+ minute: "0",
+ hour: "9",
+ day: "?",
+ month: "*",
+ weekday: "1",
+ },
+ },
+ {
+ label: "每月1日0点",
+ config: {
+ second: "0",
+ minute: "0",
+ hour: "0",
+ day: "1",
+ month: "*",
+ weekday: "?",
+ },
+ },
+ {
+ label: "工作日9点",
+ config: {
+ second: "0",
+ minute: "0",
+ hour: "9",
+ day: "?",
+ month: "*",
+ weekday: "1-5",
+ },
+ },
+ {
+ label: "每15分钟",
+ config: {
+ second: "0",
+ minute: "*/15",
+ hour: "*",
+ day: "*",
+ month: "*",
+ weekday: "?",
+ },
+ },
+ ];
+
+ const fields: Array = [
+ "second",
+ "minute",
+ "hour",
+ "day",
+ "month",
+ "weekday",
+ ];
+ if (showYear) fields.push("year");
+
+ return (
+
+
+ {/* 标题和切换模式 */}
+
+
+ 高级 Cron 表达式配置
+
+
+
+
+ {/* 快速预设 */}
+
+
快速预设:
+
+ {commonPresets.map((preset, index) => (
+
+ ))}
+
+
+
+ {customMode ? (
+ /* 手动编辑模式 */
+
+ Cron 表达式:
+
+ handleFieldChange("cronExpression", e.target.value)
+ }
+ placeholder="秒 分 时 日 月 周 [年]"
+ />
+
+ 格式:秒(0-59) 分(0-59) 时(0-23) 日(1-31) 月(1-12) 周(0-7)
+ [年(1970-2099)]
+
+
+ ) : (
+ /* 向导模式 */
+
+ {fields.map((field) => {
+ const fieldConfig = CRON_OPTIONS[field];
+ const options = generateCommonOptions(field);
+ return (
+
+
+
+ {fieldConfig.label}
+
+
+
+
+
+ {/* 自定义输入 */}
+
handleFieldChange(field, e.target.value)}
+ status={
+ validateCronField(config[field] || "", field) ? "" : "error"
+ }
+ />
+
+
+ );
+ })}
+
+ )}
+
+
+
+ {/* 结果预览 */}
+
+ 生成的 Cron 表达式:
+
+
+ 描述:{parseCronDescription(config.cronExpression)}
+
+
+
+ {/* 字段说明 */}
+
+
字段说明:
+
+
+ • * 表示任意值
+ • ? 表示不指定值(仅日、周字段)
+ • */5 表示每5个单位
+ • 1-5 表示范围
+ • 1,3,5 表示列表
+ • 日和周字段不能同时指定具体值
+
+
+
+
+
+ );
+};
+
+export default AdvancedCronScheduler;
diff --git a/frontend/src/components/CardView.tsx b/frontend/src/components/CardView.tsx
index 0219c6eee..04301cb70 100644
--- a/frontend/src/components/CardView.tsx
+++ b/frontend/src/components/CardView.tsx
@@ -1,292 +1,292 @@
-import React, { useState, useEffect, useRef } from "react";
-import { Tag, Pagination, Tooltip, Empty, Popover, Spin } from "antd";
-import { ClockCircleOutlined, StarFilled } from "@ant-design/icons";
-import type { ItemType } from "antd/es/menu/interface";
-import { formatDateTime } from "@/utils/unit";
-import ActionDropdown from "./ActionDropdown";
-import { Database } from "lucide-react";
-
-interface BaseCardDataType {
- id: string | number;
- name: string;
- type: string;
- icon?: React.JSX.Element;
- iconColor?: string;
- status: {
- label: string;
- icon?: React.JSX.Element;
- color?: string;
- } | null;
- description: string;
- tags?: string[];
- statistics?: { label: string; value: string | number }[];
- updatedAt?: string;
-}
-
-interface CardViewProps {
- data: T[];
- pagination: {
- [key: string]: any;
- current: number;
- pageSize: number;
- total: number;
- };
- operations:
- | {
- key: string;
- label: string;
- danger?: boolean;
- icon?: React.JSX.Element;
- onClick?: (item: T) => void;
- }[]
- | ((item: T) => ItemType[]);
- loading?: boolean;
- onView?: (item: T) => void;
- onFavorite?: (item: T) => void;
- isFavorite?: (item: T) => boolean;
-}
-
-// 标签渲染组件
-const TagsRenderer = ({ tags }: { tags?: any[] }) => {
- const [visibleTags, setVisibleTags] = useState([]);
- const [hiddenTags, setHiddenTags] = useState([]);
- const containerRef = useRef(null);
-
- useEffect(() => {
- if (!tags || tags.length === 0) return;
-
- const calculateVisibleTags = () => {
- if (!containerRef.current) return;
-
- const containerWidth = containerRef.current.offsetWidth;
- const tempDiv = document.createElement("div");
- tempDiv.style.visibility = "hidden";
- tempDiv.style.position = "absolute";
- tempDiv.style.top = "-9999px";
- tempDiv.className = "flex flex-wrap gap-1";
- document.body.appendChild(tempDiv);
-
- let totalWidth = 0;
- let visibleCount = 0;
- const tagElements: HTMLElement[] = [];
-
- // 为每个tag创建临时元素来测量宽度
- tags.forEach((tag, index) => {
- const tagElement = document.createElement("span");
- tagElement.className = "ant-tag ant-tag-default";
- tagElement.style.margin = "2px";
- tagElement.textContent = typeof tag === "string" ? tag : tag.name;
- tempDiv.appendChild(tagElement);
- tagElements.push(tagElement);
-
- const tagWidth = tagElement.offsetWidth + 4; // 加上gap的宽度
-
- // 如果不是最后一个标签,需要预留+n标签的空间
- const plusTagWidth = index < tags.length - 1 ? 35 : 0; // +n标签大约35px宽度
-
- if (totalWidth + tagWidth + plusTagWidth <= containerWidth) {
- totalWidth += tagWidth;
- visibleCount++;
- } else {
- // 如果当前标签放不下,且已经有可见标签,则停止
- if (visibleCount > 0) return;
- // 如果是第一个标签就放不下,至少显示一个
- if (index === 0) {
- totalWidth += tagWidth;
- visibleCount = 1;
- }
- }
- });
-
- document.body.removeChild(tempDiv);
-
- setVisibleTags(tags.slice(0, visibleCount));
- setHiddenTags(tags.slice(visibleCount));
- };
-
- // 延迟执行以确保DOM已渲染
- const timer = setTimeout(calculateVisibleTags, 0);
-
- // 监听窗口大小变化
- const handleResize = () => {
- calculateVisibleTags();
- };
-
- window.addEventListener("resize", handleResize);
-
- return () => {
- clearTimeout(timer);
- window.removeEventListener("resize", handleResize);
- };
- }, [tags]);
-
- if (!tags || tags.length === 0) return null;
-
- const popoverContent = (
-
-
- {hiddenTags.map((tag, index) => (
- {typeof tag === "string" ? tag : tag.name}
- ))}
-
-
- );
-
- return (
-
- {visibleTags.map((tag, index) => (
-
{typeof tag === "string" ? tag : tag.name}
- ))}
- {hiddenTags.length > 0 && (
-
-
- +{hiddenTags.length}
-
-
- )}
-
- );
-};
-
-function CardView(props: CardViewProps) {
- const {
- data,
- pagination,
- operations,
- loading,
- onView,
- onFavorite,
- isFavorite,
- } = props;
-
- if (data.length === 0) {
- return (
-
-
-
- );
- }
-
- const ops = (item) =>
- typeof operations === "function" ? operations(item) : operations;
-
- return (
-
-
- {data.map((item) => (
-
-
-
onView?.(item)}
- style={{ cursor: onView ? "pointer" : "default" }}
- >
- {/* Header */}
-
-
- {item?.icon && (
-
- )}
-
-
-
- {item?.name}
-
- {item?.status && (
-
-
- {item?.status?.icon && (
- {item?.status?.icon}
- )}
- {item?.status?.label}
-
-
- )}
-
-
-
- {onFavorite && (
-
onFavorite?.(item)}
- />
- )}
-
-
-
- {/* Tags */}
-
-
- {/* Description */}
-
-
- {item?.description}
-
-
-
- {/* Statistics */}
-
- {item?.statistics?.map((stat, idx) => (
-
-
- {stat?.label}:
-
-
- {stat?.value}
-
-
- ))}
-
-
-
-
- {/* Actions */}
-
-
-
- {" "}
- {formatDateTime(item?.updatedAt)}
-
-
- {operations && (
-
{
- const operation = ops(item).find((op) => op.key === key);
- if (operation?.onClick) {
- operation.onClick(item);
- }
- }}
- />
- )}
-
-
-
- ))}
-
-
-
- );
-}
-
-export default CardView;
+import React, { useState, useEffect, useRef } from "react";
+import { Tag, Pagination, Tooltip, Empty, Popover, Spin } from "antd";
+import { ClockCircleOutlined, StarFilled } from "@ant-design/icons";
+import type { ItemType } from "antd/es/menu/interface";
+import { formatDateTime } from "@/utils/unit";
+import ActionDropdown from "./ActionDropdown";
+import { Database } from "lucide-react";
+
+interface BaseCardDataType {
+ id: string | number;
+ name: string;
+ type: string;
+ icon?: React.JSX.Element;
+ iconColor?: string;
+ status: {
+ label: string;
+ icon?: React.JSX.Element;
+ color?: string;
+ } | null;
+ description: string;
+ tags?: string[];
+ statistics?: { label: string; value: string | number }[];
+ updatedAt?: string;
+}
+
+interface CardViewProps {
+ data: T[];
+ pagination: {
+ [key: string]: any;
+ current: number;
+ pageSize: number;
+ total: number;
+ };
+ operations:
+ | {
+ key: string;
+ label: string;
+ danger?: boolean;
+ icon?: React.JSX.Element;
+ onClick?: (item: T) => void;
+ }[]
+ | ((item: T) => ItemType[]);
+ loading?: boolean;
+ onView?: (item: T) => void;
+ onFavorite?: (item: T) => void;
+ isFavorite?: (item: T) => boolean;
+}
+
+// 标签渲染组件
+const TagsRenderer = ({ tags }: { tags?: any[] }) => {
+ const [visibleTags, setVisibleTags] = useState([]);
+ const [hiddenTags, setHiddenTags] = useState([]);
+ const containerRef = useRef(null);
+
+ useEffect(() => {
+ if (!tags || tags.length === 0) return;
+
+ const calculateVisibleTags = () => {
+ if (!containerRef.current) return;
+
+ const containerWidth = containerRef.current.offsetWidth;
+ const tempDiv = document.createElement("div");
+ tempDiv.style.visibility = "hidden";
+ tempDiv.style.position = "absolute";
+ tempDiv.style.top = "-9999px";
+ tempDiv.className = "flex flex-wrap gap-1";
+ document.body.appendChild(tempDiv);
+
+ let totalWidth = 0;
+ let visibleCount = 0;
+ const tagElements: HTMLElement[] = [];
+
+ // 为每个tag创建临时元素来测量宽度
+ tags.forEach((tag, index) => {
+ const tagElement = document.createElement("span");
+ tagElement.className = "ant-tag ant-tag-default";
+ tagElement.style.margin = "2px";
+ tagElement.textContent = typeof tag === "string" ? tag : tag.name;
+ tempDiv.appendChild(tagElement);
+ tagElements.push(tagElement);
+
+ const tagWidth = tagElement.offsetWidth + 4; // 加上gap的宽度
+
+ // 如果不是最后一个标签,需要预留+n标签的空间
+ const plusTagWidth = index < tags.length - 1 ? 35 : 0; // +n标签大约35px宽度
+
+ if (totalWidth + tagWidth + plusTagWidth <= containerWidth) {
+ totalWidth += tagWidth;
+ visibleCount++;
+ } else {
+ // 如果当前标签放不下,且已经有可见标签,则停止
+ if (visibleCount > 0) return;
+ // 如果是第一个标签就放不下,至少显示一个
+ if (index === 0) {
+ totalWidth += tagWidth;
+ visibleCount = 1;
+ }
+ }
+ });
+
+ document.body.removeChild(tempDiv);
+
+ setVisibleTags(tags.slice(0, visibleCount));
+ setHiddenTags(tags.slice(visibleCount));
+ };
+
+ // 延迟执行以确保DOM已渲染
+ const timer = setTimeout(calculateVisibleTags, 0);
+
+ // 监听窗口大小变化
+ const handleResize = () => {
+ calculateVisibleTags();
+ };
+
+ window.addEventListener("resize", handleResize);
+
+ return () => {
+ clearTimeout(timer);
+ window.removeEventListener("resize", handleResize);
+ };
+ }, [tags]);
+
+ if (!tags || tags.length === 0) return null;
+
+ const popoverContent = (
+
+
+ {hiddenTags.map((tag, index) => (
+ {typeof tag === "string" ? tag : tag.name}
+ ))}
+
+
+ );
+
+ return (
+
+ {visibleTags.map((tag, index) => (
+
{typeof tag === "string" ? tag : tag.name}
+ ))}
+ {hiddenTags.length > 0 && (
+
+
+ +{hiddenTags.length}
+
+
+ )}
+
+ );
+};
+
+function CardView(props: CardViewProps) {
+ const {
+ data,
+ pagination,
+ operations,
+ loading,
+ onView,
+ onFavorite,
+ isFavorite,
+ } = props;
+
+ if (data.length === 0) {
+ return (
+
+
+
+ );
+ }
+
+ const ops = (item) =>
+ typeof operations === "function" ? operations(item) : operations;
+
+ return (
+
+
+ {data.map((item) => (
+
+
+
onView?.(item)}
+ style={{ cursor: onView ? "pointer" : "default" }}
+ >
+ {/* Header */}
+
+
+ {item?.icon && (
+
+ )}
+
+
+
+ {item?.name}
+
+ {item?.status && (
+
+
+ {item?.status?.icon && (
+ {item?.status?.icon}
+ )}
+ {item?.status?.label}
+
+
+ )}
+
+
+
+ {onFavorite && (
+
onFavorite?.(item)}
+ />
+ )}
+
+
+
+ {/* Tags */}
+
+
+ {/* Description */}
+
+
+ {item?.description}
+
+
+
+ {/* Statistics */}
+
+ {item?.statistics?.map((stat, idx) => (
+
+
+ {stat?.label}:
+
+
+ {stat?.value}
+
+
+ ))}
+
+
+
+
+ {/* Actions */}
+
+
+
+ {" "}
+ {formatDateTime(item?.updatedAt)}
+
+
+ {operations && (
+
{
+ const operation = ops(item).find((op) => op.key === key);
+ if (operation?.onClick) {
+ operation.onClick(item);
+ }
+ }}
+ />
+ )}
+
+
+
+ ))}
+
+
+
+ );
+}
+
+export default CardView;
diff --git a/frontend/src/components/DetailHeader.tsx b/frontend/src/components/DetailHeader.tsx
index a95dc10f6..27b59659d 100644
--- a/frontend/src/components/DetailHeader.tsx
+++ b/frontend/src/components/DetailHeader.tsx
@@ -1,145 +1,145 @@
-import React from "react";
-import { Database } from "lucide-react";
-import { Card, Button, Tag, Tooltip, Popconfirm } from "antd";
-import type { ItemType } from "antd/es/menu/interface";
-import AddTagPopover from "./AddTagPopover";
-import ActionDropdown from "./ActionDropdown";
-
-interface StatisticItem {
- icon: React.ReactNode;
- label: string;
- value: string | number;
-}
-
-interface OperationItem {
- key: string;
- label: string;
- icon?: React.ReactNode;
- isDropdown?: boolean;
- items?: ItemType[];
- onMenuClick?: (key: string) => void;
- onClick?: () => void;
- danger?: boolean;
-}
-
-interface TagConfig {
- showAdd: boolean;
- tags: { id: number; name: string; color: string }[];
- onFetchTags?: () => Promise<{
- data: { id: number; name: string; color: string }[];
- }>;
- onAddTag?: (tag: { id: number; name: string; color: string }) => void;
- onCreateAndTag?: (tagName: string) => void;
-}
-interface DetailHeaderProps {
- data: T;
- statistics: StatisticItem[];
- operations: OperationItem[];
- tagConfig?: TagConfig;
-}
-
-function DetailHeader({
- data = {} as T,
- statistics,
- operations,
- tagConfig,
-}: DetailHeaderProps): React.ReactNode {
- return (
-
-
-
-
- {
{data?.icon}
|| (
-
- )}
-
-
-
-
{data?.name}
- {data?.status && (
-
-
- {data.status?.icon && {data.status?.icon}}
- {data.status?.label}
-
-
- )}
-
- {data?.tags && (
-
- {data?.tags?.map((tag) => (
-
- {tag.name}
-
- ))}
- {tagConfig?.showAdd && (
-
- )}
-
- )}
-
{data?.description}
-
- {statistics.map((stat) => (
-
- {stat.icon}
- {stat.value}
-
- ))}
-
-
-
-
- {operations.map((op) => {
- if (op.isDropdown) {
- return (
-
- );
- }
- if (op.confirm) {
- return (
-
- {
- if (op.onClick) {
- op.onClick()
- } else {
- op?.confirm?.onConfirm?.();
- }
- }}
- okType={op.danger ? "danger" : "primary"}
- overlayStyle={{ zIndex: 9999 }}
- >
-
-
-
- );
- }
- return (
-
-
-
- );
- })}
-
-
-
- );
-}
-
-export default DetailHeader;
+import React from "react";
+import { Database } from "lucide-react";
+import { Card, Button, Tag, Tooltip, Popconfirm } from "antd";
+import type { ItemType } from "antd/es/menu/interface";
+import AddTagPopover from "./AddTagPopover";
+import ActionDropdown from "./ActionDropdown";
+
+interface StatisticItem {
+ icon: React.ReactNode;
+ label: string;
+ value: string | number;
+}
+
+interface OperationItem {
+ key: string;
+ label: string;
+ icon?: React.ReactNode;
+ isDropdown?: boolean;
+ items?: ItemType[];
+ onMenuClick?: (key: string) => void;
+ onClick?: () => void;
+ danger?: boolean;
+}
+
+interface TagConfig {
+ showAdd: boolean;
+ tags: { id: number; name: string; color: string }[];
+ onFetchTags?: () => Promise<{
+ data: { id: number; name: string; color: string }[];
+ }>;
+ onAddTag?: (tag: { id: number; name: string; color: string }) => void;
+ onCreateAndTag?: (tagName: string) => void;
+}
+interface DetailHeaderProps {
+ data: T;
+ statistics: StatisticItem[];
+ operations: OperationItem[];
+ tagConfig?: TagConfig;
+}
+
+function DetailHeader({
+ data = {} as T,
+ statistics,
+ operations,
+ tagConfig,
+}: DetailHeaderProps): React.ReactNode {
+ return (
+
+
+
+
+ {
{data?.icon}
|| (
+
+ )}
+
+
+
+
{data?.name}
+ {data?.status && (
+
+
+ {data.status?.icon && {data.status?.icon}}
+ {data.status?.label}
+
+
+ )}
+
+ {data?.tags && (
+
+ {data?.tags?.map((tag) => (
+
+ {tag.name}
+
+ ))}
+ {tagConfig?.showAdd && (
+
+ )}
+
+ )}
+
{data?.description}
+
+ {statistics.map((stat) => (
+
+ {stat.icon}
+ {stat.value}
+
+ ))}
+
+
+
+
+ {operations.map((op) => {
+ if (op.isDropdown) {
+ return (
+
+ );
+ }
+ if (op.confirm) {
+ return (
+
+ {
+ if (op.onClick) {
+ op.onClick()
+ } else {
+ op?.confirm?.onConfirm?.();
+ }
+ }}
+ okType={op.danger ? "danger" : "primary"}
+ overlayStyle={{ zIndex: 9999 }}
+ >
+
+
+
+ );
+ }
+ return (
+
+
+
+ );
+ })}
+
+
+
+ );
+}
+
+export default DetailHeader;
diff --git a/frontend/src/components/DevelopmentInProgress.tsx b/frontend/src/components/DevelopmentInProgress.tsx
index 81303b7f7..a0a01d8b5 100644
--- a/frontend/src/components/DevelopmentInProgress.tsx
+++ b/frontend/src/components/DevelopmentInProgress.tsx
@@ -1,28 +1,28 @@
-import { Button } from "antd";
-const DevelopmentInProgress = ({ showHome = true, showTime = "" }) => {
- return (
-
-
🚧
-
功能开发中
- {showTime && (
-
- 为了给您带来更好的体验,我们计划{showTime}
- 开放此功能
-
- )}
- {showHome && (
-
- )}
-
- );
-};
-
-export default DevelopmentInProgress;
+import { Button } from "antd";
+const DevelopmentInProgress = ({ showHome = true, showTime = "" }) => {
+ return (
+
+
🚧
+
功能开发中
+ {showTime && (
+
+ 为了给您带来更好的体验,我们计划{showTime}
+ 开放此功能
+
+ )}
+ {showHome && (
+
+ )}
+
+ );
+};
+
+export default DevelopmentInProgress;
diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx
index 7538c195f..6c7e2f9f8 100644
--- a/frontend/src/components/ErrorBoundary.tsx
+++ b/frontend/src/components/ErrorBoundary.tsx
@@ -1,191 +1,191 @@
-import React, { Component } from "react";
-import { Button, Modal } from "antd";
-
-interface ErrorContextType {
- hasError: boolean;
- error: Error | null;
- errorInfo: { componentStack: string } | null;
-}
-
-const ErrorContext = React.createContext({
- hasError: false,
- error: null,
- errorInfo: null,
-});
-
-interface ErrorBoundaryState {
- hasError: boolean;
- error: Error | null;
- errorInfo: { componentStack: string } | null;
- errorTimestamp: string | null;
-}
-
-interface ErrorBoundaryProps {
- children?: React.ReactNode;
- onReset?: () => void;
- showDetails?: boolean;
-}
-
-export default class ErrorBoundary extends Component<
- ErrorBoundaryProps,
- ErrorBoundaryState
-> {
- constructor(props: ErrorBoundaryProps) {
- super(props);
- this.state = {
- hasError: false,
- error: null,
- errorInfo: null,
- errorTimestamp: null,
- };
- }
-
- static getDerivedStateFromError(error: any) {
- // 更新 state 使下一次渲染能够显示降级 UI
- return {
- hasError: true,
- error: error,
- errorTimestamp: new Date().toISOString(),
- };
- }
-
- componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
- // 错误统计
- this.setState({
- error,
- errorInfo,
- hasError: true,
- });
-
- // 在实际应用中,这里可以集成错误报告服务
- this.logErrorToService(error, errorInfo);
-
- // 开发环境下在控制台显示详细错误
- if (process.env.NODE_ENV === "development") {
- console.error("ErrorBoundary 捕获到错误:", error);
- console.error("错误详情:", errorInfo);
- }
- }
-
- logErrorToService = (error: Error, errorInfo: React.ErrorInfo) => {
- // 这里可以集成 Sentry、LogRocket 等错误监控服务
- const errorData = {
- error: error.toString(),
- errorInfo: errorInfo.componentStack,
- timestamp: this.state.errorTimestamp,
- url: window.location.href,
- userAgent: navigator.userAgent,
- };
-
- // 模拟发送错误日志
- console.log("发送错误日志到监控服务:", errorData);
-
- // 实际使用时取消注释并配置您的错误监控服务
- /*
- if (window.Sentry) {
- window.Sentry.captureException(error, { extra: errorInfo });
- }
- */
- };
-
- handleReset = () => {
- this.setState({
- hasError: false,
- error: null,
- errorInfo: null,
- errorTimestamp: null,
- });
-
- // 可选:重新加载页面或执行其他恢复操作
- if (this.props.onReset) {
- this.props.onReset();
- }
- };
-
- handleReload = () => {
- window.location.reload();
- };
-
- handleGoHome = () => {
- window.location.href = "/";
- };
-
- renderErrorDetails = () => {
- const { error, errorInfo } = this.state;
-
- if (!this.props.showDetails) return null;
-
- return (
-
-
-
错误信息:
-
- {error?.toString()}
-
-
- {errorInfo && (
-
-
组件堆栈:
-
- {errorInfo.componentStack}
-
-
- )}
-
- );
- };
-
- render() {
- if (this.state.hasError) {
- return (
-
-
-
⚠️
-
出了点问题
-
应用程序遇到了意外错误。
-
-
-
-
-
-
- {this.renderErrorDetails()}
-
-
-
- 如果问题持续存在,请联系技术支持
-
-
- 错误 ID: {this.state.errorTimestamp}
-
-
-
-
- );
- }
-
- return (
-
- {this.props.children}
-
- );
- }
-}
-
-export function withErrorBoundary(
- Component: React.ComponentType
-): React.ComponentType {
- return (props) => (
-
-
-
- );
-}
+import React, { Component } from "react";
+import { Button, Modal } from "antd";
+
+interface ErrorContextType {
+ hasError: boolean;
+ error: Error | null;
+ errorInfo: { componentStack: string } | null;
+}
+
+const ErrorContext = React.createContext({
+ hasError: false,
+ error: null,
+ errorInfo: null,
+});
+
+interface ErrorBoundaryState {
+ hasError: boolean;
+ error: Error | null;
+ errorInfo: { componentStack: string } | null;
+ errorTimestamp: string | null;
+}
+
+interface ErrorBoundaryProps {
+ children?: React.ReactNode;
+ onReset?: () => void;
+ showDetails?: boolean;
+}
+
+export default class ErrorBoundary extends Component<
+ ErrorBoundaryProps,
+ ErrorBoundaryState
+> {
+ constructor(props: ErrorBoundaryProps) {
+ super(props);
+ this.state = {
+ hasError: false,
+ error: null,
+ errorInfo: null,
+ errorTimestamp: null,
+ };
+ }
+
+ static getDerivedStateFromError(error: any) {
+ // 更新 state 使下一次渲染能够显示降级 UI
+ return {
+ hasError: true,
+ error: error,
+ errorTimestamp: new Date().toISOString(),
+ };
+ }
+
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+ // 错误统计
+ this.setState({
+ error,
+ errorInfo,
+ hasError: true,
+ });
+
+ // 在实际应用中,这里可以集成错误报告服务
+ this.logErrorToService(error, errorInfo);
+
+ // 开发环境下在控制台显示详细错误
+ if (process.env.NODE_ENV === "development") {
+ console.error("ErrorBoundary 捕获到错误:", error);
+ console.error("错误详情:", errorInfo);
+ }
+ }
+
+ logErrorToService = (error: Error, errorInfo: React.ErrorInfo) => {
+ // 这里可以集成 Sentry、LogRocket 等错误监控服务
+ const errorData = {
+ error: error.toString(),
+ errorInfo: errorInfo.componentStack,
+ timestamp: this.state.errorTimestamp,
+ url: window.location.href,
+ userAgent: navigator.userAgent,
+ };
+
+ // 模拟发送错误日志
+ console.log("发送错误日志到监控服务:", errorData);
+
+ // 实际使用时取消注释并配置您的错误监控服务
+ /*
+ if (window.Sentry) {
+ window.Sentry.captureException(error, { extra: errorInfo });
+ }
+ */
+ };
+
+ handleReset = () => {
+ this.setState({
+ hasError: false,
+ error: null,
+ errorInfo: null,
+ errorTimestamp: null,
+ });
+
+ // 可选:重新加载页面或执行其他恢复操作
+ if (this.props.onReset) {
+ this.props.onReset();
+ }
+ };
+
+ handleReload = () => {
+ window.location.reload();
+ };
+
+ handleGoHome = () => {
+ window.location.href = "/";
+ };
+
+ renderErrorDetails = () => {
+ const { error, errorInfo } = this.state;
+
+ if (!this.props.showDetails) return null;
+
+ return (
+
+
+
错误信息:
+
+ {error?.toString()}
+
+
+ {errorInfo && (
+
+
组件堆栈:
+
+ {errorInfo.componentStack}
+
+
+ )}
+
+ );
+ };
+
+ render() {
+ if (this.state.hasError) {
+ return (
+
+
+
⚠️
+
出了点问题
+
应用程序遇到了意外错误。
+
+
+
+
+
+
+ {this.renderErrorDetails()}
+
+
+
+ 如果问题持续存在,请联系技术支持
+
+
+ 错误 ID: {this.state.errorTimestamp}
+
+
+
+
+ );
+ }
+
+ return (
+
+ {this.props.children}
+
+ );
+ }
+}
+
+export function withErrorBoundary(
+ Component: React.ComponentType
+): React.ComponentType {
+ return (props) => (
+
+
+
+ );
+}
diff --git a/frontend/src/components/RadioCard.tsx b/frontend/src/components/RadioCard.tsx
index f4a441446..ad9f8d806 100644
--- a/frontend/src/components/RadioCard.tsx
+++ b/frontend/src/components/RadioCard.tsx
@@ -1,70 +1,70 @@
-import React from "react";
-import { Card } from "antd";
-
-interface RadioCardOption {
- value: string;
- label: string;
- description?: string;
- icon?: SVGAElement | React.FC>;
- color?: string;
-}
-
-interface RadioCardProps {
- options: RadioCardOption[];
- value: string;
- onChange: (value: string) => void;
- className?: string;
-}
-
-const RadioCard: React.FC = ({
- options,
- value,
- onChange,
- className,
-}) => {
- return (
-
- {options.map((option) => (
-
onChange(option.value)}
- >
-
-
- {option.label}
-
- {option.description && (
-
- {option.description}
-
- )}
-
- ))}
-
- );
-};
-
-export default RadioCard;
+import React from "react";
+import { Card } from "antd";
+
+interface RadioCardOption {
+ value: string;
+ label: string;
+ description?: string;
+ icon?: SVGAElement | React.FC>;
+ color?: string;
+}
+
+interface RadioCardProps {
+ options: RadioCardOption[];
+ value: string;
+ onChange: (value: string) => void;
+ className?: string;
+}
+
+const RadioCard: React.FC = ({
+ options,
+ value,
+ onChange,
+ className,
+}) => {
+ return (
+
+ {options.map((option) => (
+
onChange(option.value)}
+ >
+
+
+ {option.label}
+
+ {option.description && (
+
+ {option.description}
+
+ )}
+
+ ))}
+
+ );
+};
+
+export default RadioCard;
diff --git a/frontend/src/components/SearchControls.tsx b/frontend/src/components/SearchControls.tsx
index ff8e92be2..3e2c4d7f6 100644
--- a/frontend/src/components/SearchControls.tsx
+++ b/frontend/src/components/SearchControls.tsx
@@ -1,239 +1,239 @@
-import { Input, Button, Select, Tag, Segmented, DatePicker } from "antd";
-import {
- BarsOutlined,
- AppstoreOutlined,
- SearchOutlined,
- ReloadOutlined,
-} from "@ant-design/icons";
-import { useEffect, useState } from "react";
-
-interface FilterOption {
- key: string;
- label: string;
- mode?: "tags" | "multiple";
- options: { label: string; value: string }[];
-}
-
-interface SearchControlsProps {
- searchTerm: string;
- onSearchChange: (value: string) => void;
- searchPlaceholder?: string;
-
- // Filter props
- filters?: FilterOption[];
- selectedFilters?: Record;
- onFiltersChange?: (filters: Record) => void;
- onClearFilters?: () => void;
-
- // Date range props
- dateRange?: [Date | null, Date | null] | null;
- onDateChange?: (dates: [Date | null, Date | null] | null) => void;
-
- // Reload props
- onReload?: () => void;
-
- // View props
- viewMode?: "card" | "list";
- onViewModeChange?: (mode: "card" | "list") => void;
-
- // Control visibility
- showFilters?: boolean;
- showSort?: boolean;
- showViewToggle?: boolean;
- showReload?: boolean;
- showDatePicker?: boolean;
-
- // Styling
- className?: string;
-}
-
-export function SearchControls({
- viewMode,
- className,
- searchTerm,
- showFilters = true,
- showViewToggle = true,
- searchPlaceholder = "搜索...",
- filters = [],
- dateRange,
- showDatePicker = false,
- showReload = true,
- onReload,
- onDateChange,
- onSearchChange,
- onFiltersChange,
- onViewModeChange,
- onClearFilters,
-}: SearchControlsProps) {
- const [selectedFilters, setSelectedFilters] = useState<{
- [key: string]: string[];
- }>({});
-
- const filtersMap: Record = filters.reduce(
- (prev, cur) => ({ ...prev, [cur.key]: cur }),
- {}
- );
-
- // select change
- const handleFilterChange = (filterKey: string, value: string) => {
- const filteredValues = {
- ...selectedFilters,
- [filterKey]: !value ? [] : [value],
- };
- setSelectedFilters(filteredValues);
- };
-
- // 清除已选筛选
- const handleClearFilter = (filterKey: string, value: string | string[]) => {
- const isMultiple = filtersMap[filterKey]?.mode === "multiple";
- if (!isMultiple) {
- setSelectedFilters({
- ...selectedFilters,
- [filterKey]: [],
- });
- } else {
- const currentValues = selectedFilters[filterKey]?.[0] || [];
- const newValues = currentValues.filter((v) => v !== value);
- setSelectedFilters({
- ...selectedFilters,
- [filterKey]: [newValues],
- });
- }
- };
-
- const handleClearAllFilters = () => {
- setSelectedFilters({});
- onClearFilters?.();
- };
-
- const hasActiveFilters = Object.values(selectedFilters).some(
- (values) => values?.[0]?.length > 0
- );
-
- useEffect(() => {
- if (Object.keys(selectedFilters).length === 0) return;
- onFiltersChange?.(selectedFilters);
- }, [selectedFilters]);
-
- return (
-
-
- {/* Left side - Search and Filters */}
-
- {/* Search */}
-
- onSearchChange(e.target.value)}
- prefix={}
- />
-
-
- {/* Filters */}
- {showFilters && filters.length > 0 && (
-
- {filters.map((filter: FilterOption) => (
-
- ))}
-
- )}
-
-
- {showDatePicker && (
-
- )}
-
- {/* Right side */}
-
- {showViewToggle && onViewModeChange && (
-
},
- { value: "card", icon:
},
- ]}
- value={viewMode}
- onChange={(value) => onViewModeChange(value as "list" | "card")}
- />
- )}
-
- {showReload && (
-
}
- onClick={() => onReload?.()}
- >
- )}
-
-
-
- {/* Active Filters Display */}
- {hasActiveFilters && (
-
-
-
-
- 已选筛选:
-
- {Object.entries(selectedFilters).map(([filterKey, values]) =>
- values.map((value) => {
- const filter = filtersMap[filterKey];
-
- const getLabeledValue = (item: string) => {
- const option = filter?.options.find(
- (o) => o.value === item
- );
- return (
- handleClearFilter(filterKey, item)}
- color="blue"
- >
- {filter?.label}: {option?.label || item}
-
- );
- };
- return Array.isArray(value)
- ? value.map((item) => getLabeledValue(item))
- : getLabeledValue(value);
- })
- )}
-
-
- {/* Clear all filters button on the right */}
-
-
-
- )}
-
- );
-}
+import { Input, Button, Select, Tag, Segmented, DatePicker } from "antd";
+import {
+ BarsOutlined,
+ AppstoreOutlined,
+ SearchOutlined,
+ ReloadOutlined,
+} from "@ant-design/icons";
+import { useEffect, useState } from "react";
+
+interface FilterOption {
+ key: string;
+ label: string;
+ mode?: "tags" | "multiple";
+ options: { label: string; value: string }[];
+}
+
+interface SearchControlsProps {
+ searchTerm: string;
+ onSearchChange: (value: string) => void;
+ searchPlaceholder?: string;
+
+ // Filter props
+ filters?: FilterOption[];
+ selectedFilters?: Record;
+ onFiltersChange?: (filters: Record) => void;
+ onClearFilters?: () => void;
+
+ // Date range props
+ dateRange?: [Date | null, Date | null] | null;
+ onDateChange?: (dates: [Date | null, Date | null] | null) => void;
+
+ // Reload props
+ onReload?: () => void;
+
+ // View props
+ viewMode?: "card" | "list";
+ onViewModeChange?: (mode: "card" | "list") => void;
+
+ // Control visibility
+ showFilters?: boolean;
+ showSort?: boolean;
+ showViewToggle?: boolean;
+ showReload?: boolean;
+ showDatePicker?: boolean;
+
+ // Styling
+ className?: string;
+}
+
+export function SearchControls({
+ viewMode,
+ className,
+ searchTerm,
+ showFilters = true,
+ showViewToggle = true,
+ searchPlaceholder = "搜索...",
+ filters = [],
+ dateRange,
+ showDatePicker = false,
+ showReload = true,
+ onReload,
+ onDateChange,
+ onSearchChange,
+ onFiltersChange,
+ onViewModeChange,
+ onClearFilters,
+}: SearchControlsProps) {
+ const [selectedFilters, setSelectedFilters] = useState<{
+ [key: string]: string[];
+ }>({});
+
+ const filtersMap: Record = filters.reduce(
+ (prev, cur) => ({ ...prev, [cur.key]: cur }),
+ {}
+ );
+
+ // select change
+ const handleFilterChange = (filterKey: string, value: string) => {
+ const filteredValues = {
+ ...selectedFilters,
+ [filterKey]: !value ? [] : [value],
+ };
+ setSelectedFilters(filteredValues);
+ };
+
+ // 清除已选筛选
+ const handleClearFilter = (filterKey: string, value: string | string[]) => {
+ const isMultiple = filtersMap[filterKey]?.mode === "multiple";
+ if (!isMultiple) {
+ setSelectedFilters({
+ ...selectedFilters,
+ [filterKey]: [],
+ });
+ } else {
+ const currentValues = selectedFilters[filterKey]?.[0] || [];
+ const newValues = currentValues.filter((v) => v !== value);
+ setSelectedFilters({
+ ...selectedFilters,
+ [filterKey]: [newValues],
+ });
+ }
+ };
+
+ const handleClearAllFilters = () => {
+ setSelectedFilters({});
+ onClearFilters?.();
+ };
+
+ const hasActiveFilters = Object.values(selectedFilters).some(
+ (values) => values?.[0]?.length > 0
+ );
+
+ useEffect(() => {
+ if (Object.keys(selectedFilters).length === 0) return;
+ onFiltersChange?.(selectedFilters);
+ }, [selectedFilters]);
+
+ return (
+
+
+ {/* Left side - Search and Filters */}
+
+ {/* Search */}
+
+ onSearchChange(e.target.value)}
+ prefix={}
+ />
+
+
+ {/* Filters */}
+ {showFilters && filters.length > 0 && (
+
+ {filters.map((filter: FilterOption) => (
+
+ ))}
+
+ )}
+
+
+ {showDatePicker && (
+
+ )}
+
+ {/* Right side */}
+
+ {showViewToggle && onViewModeChange && (
+
},
+ { value: "card", icon:
},
+ ]}
+ value={viewMode}
+ onChange={(value) => onViewModeChange(value as "list" | "card")}
+ />
+ )}
+
+ {showReload && (
+
}
+ onClick={() => onReload?.()}
+ >
+ )}
+
+
+
+ {/* Active Filters Display */}
+ {hasActiveFilters && (
+
+
+
+
+ 已选筛选:
+
+ {Object.entries(selectedFilters).map(([filterKey, values]) =>
+ values.map((value) => {
+ const filter = filtersMap[filterKey];
+
+ const getLabeledValue = (item: string) => {
+ const option = filter?.options.find(
+ (o) => o.value === item
+ );
+ return (
+ handleClearFilter(filterKey, item)}
+ color="blue"
+ >
+ {filter?.label}: {option?.label || item}
+
+ );
+ };
+ return Array.isArray(value)
+ ? value.map((item) => getLabeledValue(item))
+ : getLabeledValue(value);
+ })
+ )}
+
+
+ {/* Clear all filters button on the right */}
+
+
+
+ )}
+
+ );
+}
diff --git a/frontend/src/components/TagList.tsx b/frontend/src/components/TagList.tsx
index 9688c776f..75fa14076 100644
--- a/frontend/src/components/TagList.tsx
+++ b/frontend/src/components/TagList.tsx
@@ -1,149 +1,149 @@
-import React, { useEffect, useRef, useState } from "react";
-import { PlusOutlined } from "@ant-design/icons";
-import type { InputRef } from "antd";
-import { Flex, Input, Tag, theme, Tooltip } from "antd";
-
-const tagInputStyle: React.CSSProperties = {
- width: 64,
- height: 22,
- marginInlineEnd: 8,
- verticalAlign: "top",
-};
-
-interface TagListProps {
- tags: string[];
- setTags: (tags: string[]) => void;
- onDelete?: (tag: string) => void;
- onAdd?: (tag: string) => void;
- onEdit?: (oldTag: string, newTag: string) => void;
-}
-
-const TagList: React.FC = ({
- tags,
- setTags,
- onDelete,
- onAdd,
- onEdit,
-}) => {
- const { token } = theme.useToken();
- const [inputVisible, setInputVisible] = useState(false);
- const [inputValue, setInputValue] = useState("");
- const [editInputIndex, setEditInputIndex] = useState(-1);
- const [editInputValue, setEditInputValue] = useState("");
- const inputRef = useRef(null);
- const editInputRef = useRef(null);
-
- useEffect(() => {
- if (inputVisible) {
- inputRef.current?.focus();
- }
- }, [inputVisible]);
-
- useEffect(() => {
- editInputRef.current?.focus();
- }, [editInputValue]);
-
- const handleClose = (removedTag: string) => {
- const newTags = tags.filter((tag) => tag !== removedTag);
- setTags(newTags);
- onDelete?.(removedTag);
- };
-
- const showInput = () => {
- setInputVisible(true);
- };
-
- const handleInputChange = (e: React.ChangeEvent) => {
- setInputValue(e.target.value);
- };
-
- const handleInputConfirm = () => {
- if (inputValue && !tags.includes(inputValue)) {
- setTags([...tags, inputValue]);
- onAdd?.(inputValue);
- }
- setInputVisible(false);
- setInputValue("");
- };
-
- const handleEditInputChange = (e: React.ChangeEvent) => {
- setEditInputValue(e.target.value);
- };
-
- const handleEditInputConfirm = () => {
- const newTags = [...tags];
- newTags[editInputIndex] = editInputValue;
- setTags(newTags);
- onEdit?.(tags[editInputIndex], editInputValue);
- setEditInputIndex(-1);
- setEditInputValue("");
- };
-
- const tagPlusStyle: React.CSSProperties = {
- height: 22,
- background: token.colorBgContainer,
- borderStyle: "dashed",
- };
-
- return (
-
- {tags.map((tag, index) => {
- if (editInputIndex === index) {
- return (
-
- );
- }
- const isLongTag = tag.length > 20;
- const tagElem = (
- handleClose(tag)} closable>
- {
- if (index !== 0) {
- setEditInputIndex(index);
- setEditInputValue(tag);
- e.preventDefault();
- }
- }}
- >
- {isLongTag ? `${tag.slice(0, 20)}...` : tag}
-
-
- );
- return isLongTag ? (
-
- {tagElem}
-
- ) : (
- tagElem
- );
- })}
- {inputVisible ? (
-
- ) : (
- } onClick={showInput}>
- 新增标签
-
- )}
-
- );
-};
-
-export default TagList;
+import React, { useEffect, useRef, useState } from "react";
+import { PlusOutlined } from "@ant-design/icons";
+import type { InputRef } from "antd";
+import { Flex, Input, Tag, theme, Tooltip } from "antd";
+
+const tagInputStyle: React.CSSProperties = {
+ width: 64,
+ height: 22,
+ marginInlineEnd: 8,
+ verticalAlign: "top",
+};
+
+interface TagListProps {
+ tags: string[];
+ setTags: (tags: string[]) => void;
+ onDelete?: (tag: string) => void;
+ onAdd?: (tag: string) => void;
+ onEdit?: (oldTag: string, newTag: string) => void;
+}
+
+const TagList: React.FC = ({
+ tags,
+ setTags,
+ onDelete,
+ onAdd,
+ onEdit,
+}) => {
+ const { token } = theme.useToken();
+ const [inputVisible, setInputVisible] = useState(false);
+ const [inputValue, setInputValue] = useState("");
+ const [editInputIndex, setEditInputIndex] = useState(-1);
+ const [editInputValue, setEditInputValue] = useState("");
+ const inputRef = useRef(null);
+ const editInputRef = useRef(null);
+
+ useEffect(() => {
+ if (inputVisible) {
+ inputRef.current?.focus();
+ }
+ }, [inputVisible]);
+
+ useEffect(() => {
+ editInputRef.current?.focus();
+ }, [editInputValue]);
+
+ const handleClose = (removedTag: string) => {
+ const newTags = tags.filter((tag) => tag !== removedTag);
+ setTags(newTags);
+ onDelete?.(removedTag);
+ };
+
+ const showInput = () => {
+ setInputVisible(true);
+ };
+
+ const handleInputChange = (e: React.ChangeEvent) => {
+ setInputValue(e.target.value);
+ };
+
+ const handleInputConfirm = () => {
+ if (inputValue && !tags.includes(inputValue)) {
+ setTags([...tags, inputValue]);
+ onAdd?.(inputValue);
+ }
+ setInputVisible(false);
+ setInputValue("");
+ };
+
+ const handleEditInputChange = (e: React.ChangeEvent) => {
+ setEditInputValue(e.target.value);
+ };
+
+ const handleEditInputConfirm = () => {
+ const newTags = [...tags];
+ newTags[editInputIndex] = editInputValue;
+ setTags(newTags);
+ onEdit?.(tags[editInputIndex], editInputValue);
+ setEditInputIndex(-1);
+ setEditInputValue("");
+ };
+
+ const tagPlusStyle: React.CSSProperties = {
+ height: 22,
+ background: token.colorBgContainer,
+ borderStyle: "dashed",
+ };
+
+ return (
+
+ {tags.map((tag, index) => {
+ if (editInputIndex === index) {
+ return (
+
+ );
+ }
+ const isLongTag = tag.length > 20;
+ const tagElem = (
+ handleClose(tag)} closable>
+ {
+ if (index !== 0) {
+ setEditInputIndex(index);
+ setEditInputValue(tag);
+ e.preventDefault();
+ }
+ }}
+ >
+ {isLongTag ? `${tag.slice(0, 20)}...` : tag}
+
+
+ );
+ return isLongTag ? (
+
+ {tagElem}
+
+ ) : (
+ tagElem
+ );
+ })}
+ {inputVisible ? (
+
+ ) : (
+ } onClick={showInput}>
+ 新增标签
+
+ )}
+
+ );
+};
+
+export default TagList;
diff --git a/frontend/src/components/TopLoadingBar.tsx b/frontend/src/components/TopLoadingBar.tsx
index a921e210d..ec95fd16f 100644
--- a/frontend/src/components/TopLoadingBar.tsx
+++ b/frontend/src/components/TopLoadingBar.tsx
@@ -1,69 +1,69 @@
-import { useEffect, useRef, useState } from "react";
-
-const TopLoadingBar = () => {
- const [isVisible, setIsVisible] = useState(false);
- const [progress, setProgress] = useState(0);
- const intervalRef = useRef(null);
-
- useEffect(() => {
- // 监听全局事件
- const handleShow = () => {
- setIsVisible(true);
- setProgress(0);
-
- // 清除可能存在的旧interval
- if (intervalRef.current) {
- clearInterval(intervalRef.current);
- }
-
- // 模拟进度
- let currentProgress = 0;
- intervalRef.current = setInterval(() => {
- currentProgress += Math.random() * 10;
- if (currentProgress >= 90) {
- clearInterval(intervalRef.current);
- }
- setProgress(currentProgress);
- }, 200);
- };
-
- const handleHide = () => {
- // 清除进度interval
- if (intervalRef.current) {
- clearInterval(intervalRef.current);
- intervalRef.current = null;
- }
- setProgress(100);
- setTimeout(() => {
- setIsVisible(false);
- setProgress(0);
- }, 300);
- };
-
- // 添加全局事件监听器
- window.addEventListener("loading:show", handleShow);
- window.addEventListener("loading:hide", handleHide);
-
- return () => {
- // 组件卸载时清理
- if (intervalRef.current) {
- clearInterval(intervalRef.current);
- }
- window.removeEventListener("loading:show", handleShow);
- window.removeEventListener("loading:hide", handleHide);
- };
- }, []);
-
- if (!isVisible) return null;
-
- return (
-
- );
-};
-
-export default TopLoadingBar;
+import { useEffect, useRef, useState } from "react";
+
+const TopLoadingBar = () => {
+ const [isVisible, setIsVisible] = useState(false);
+ const [progress, setProgress] = useState(0);
+ const intervalRef = useRef(null);
+
+ useEffect(() => {
+ // 监听全局事件
+ const handleShow = () => {
+ setIsVisible(true);
+ setProgress(0);
+
+ // 清除可能存在的旧interval
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ }
+
+ // 模拟进度
+ let currentProgress = 0;
+ intervalRef.current = setInterval(() => {
+ currentProgress += Math.random() * 10;
+ if (currentProgress >= 90) {
+ clearInterval(intervalRef.current);
+ }
+ setProgress(currentProgress);
+ }, 200);
+ };
+
+ const handleHide = () => {
+ // 清除进度interval
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ intervalRef.current = null;
+ }
+ setProgress(100);
+ setTimeout(() => {
+ setIsVisible(false);
+ setProgress(0);
+ }, 300);
+ };
+
+ // 添加全局事件监听器
+ window.addEventListener("loading:show", handleShow);
+ window.addEventListener("loading:hide", handleHide);
+
+ return () => {
+ // 组件卸载时清理
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ }
+ window.removeEventListener("loading:show", handleShow);
+ window.removeEventListener("loading:hide", handleHide);
+ };
+ }, []);
+
+ if (!isVisible) return null;
+
+ return (
+
+ );
+};
+
+export default TopLoadingBar;
diff --git a/frontend/src/components/business/DatasetFileTransfer.tsx b/frontend/src/components/business/DatasetFileTransfer.tsx
index 0d222fe96..92284cb1f 100644
--- a/frontend/src/components/business/DatasetFileTransfer.tsx
+++ b/frontend/src/components/business/DatasetFileTransfer.tsx
@@ -1,331 +1,331 @@
-import React, { useCallback, useEffect } from "react";
-import { Button, Input, Table } from "antd";
-import { RightOutlined } from "@ant-design/icons";
-import { mapDataset } from "@/pages/DataManagement/dataset.const";
-import {
- Dataset,
- DatasetFile,
- DatasetType,
-} from "@/pages/DataManagement/dataset.model";
-import {
- queryDatasetFilesUsingGet,
- queryDatasetsUsingGet,
-} from "@/pages/DataManagement/dataset.api";
-import { formatBytes } from "@/utils/unit";
-import { useDebouncedEffect } from "@/hooks/useDebouncedEffect";
-
-interface DatasetFileTransferProps
- extends React.HTMLAttributes {
- open: boolean;
- selectedFilesMap: { [key: string]: DatasetFile };
- onSelectedFilesChange: (filesMap: { [key: string]: DatasetFile }) => void;
- onDatasetSelect?: (dataset: Dataset | null) => void;
-}
-
-const fileCols = [
- {
- title: "所属数据集",
- dataIndex: "datasetName",
- key: "datasetName",
- ellipsis: true,
- },
- {
- title: "文件名",
- dataIndex: "fileName",
- key: "fileName",
- ellipsis: true,
- },
- {
- title: "大小",
- dataIndex: "fileSize",
- key: "fileSize",
- ellipsis: true,
- render: formatBytes,
- },
-];
-
-// Customize Table Transfer
-const DatasetFileTransfer: React.FC = ({
- open,
- selectedFilesMap,
- onSelectedFilesChange,
- onDatasetSelect,
- ...props
-}) => {
- const [datasets, setDatasets] = React.useState([]);
- const [datasetSearch, setDatasetSearch] = React.useState("");
- const [datasetPagination, setDatasetPagination] = React.useState<{
- current: number;
- pageSize: number;
- total: number;
- }>({ current: 1, pageSize: 10, total: 0 });
-
- const [files, setFiles] = React.useState([]);
- const [filesSearch, setFilesSearch] = React.useState("");
- const [filesPagination, setFilesPagination] = React.useState<{
- current: number;
- pageSize: number;
- total: number;
- }>({ current: 1, pageSize: 10, total: 0 });
-
- const [showFiles, setShowFiles] = React.useState(false);
- const [selectedDataset, setSelectedDataset] = React.useState(
- null
- );
- const [datasetSelections, setDatasetSelections] = React.useState(
- []
- );
-
- const fetchDatasets = async () => {
- const { data } = await queryDatasetsUsingGet({
- // Ant Design Table pagination.current is 1-based; ensure backend also receives 1-based value
- page: datasetPagination.current,
- size: datasetPagination.pageSize,
- keyword: datasetSearch,
- type: DatasetType.TEXT,
- });
- setDatasets(data.content.map(mapDataset) || []);
- setDatasetPagination((prev) => ({
- ...prev,
- total: data.totalElements,
- }));
- };
-
- useDebouncedEffect(
- () => {
- fetchDatasets();
- },
- [datasetSearch, datasetPagination.pageSize, datasetPagination.current],
- 300
- );
-
- const fetchFiles = useCallback(
- async (
- options?: Partial<{ page: number; pageSize: number; keyword: string }>
- ) => {
- if (!selectedDataset) return;
- const page = options?.page ?? filesPagination.current;
- const pageSize = options?.pageSize ?? filesPagination.pageSize;
- const keyword = options?.keyword ?? filesSearch;
-
- const { data } = await queryDatasetFilesUsingGet(selectedDataset.id, {
- page,
- size: pageSize,
- keyword,
- });
- setFiles(
- (data.content || []).map((item: DatasetFile) => ({
- ...item,
- key: item.id,
- datasetName: selectedDataset.name,
- }))
- );
- setFilesPagination((prev) => ({
- ...prev,
- current: page,
- pageSize,
- total: data.totalElements,
- }));
- },
- [selectedDataset, filesPagination.current, filesPagination.pageSize, filesSearch]
- );
-
- useEffect(() => {
- // 当数据集变化时,重置文件分页并拉取第一页文件,避免额外的循环请求
- if (selectedDataset) {
- setFilesPagination({ current: 1, pageSize: 10, total: 0 });
- fetchFiles({ page: 1, pageSize: 10 }).catch(() => {});
- } else {
- setFiles([]);
- setFilesPagination({ current: 1, pageSize: 10, total: 0 });
- }
- // 只在 selectedDataset 变化时触发
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [selectedDataset]);
-
- useEffect(() => {
- onDatasetSelect?.(selectedDataset);
- }, [selectedDataset, onDatasetSelect]);
-
- const toggleSelectFile = (record: DatasetFile) => {
- if (!selectedFilesMap[record.id]) {
- onSelectedFilesChange({
- ...selectedFilesMap,
- [record.id]: record,
- });
- } else {
- const newSelectedFiles = { ...selectedFilesMap };
- delete newSelectedFiles[record.id];
- onSelectedFilesChange(newSelectedFiles);
- }
- };
-
- useEffect(() => {
- if (!open) {
- // 重置状态
- setDatasets([]);
- setDatasetSearch("");
- setDatasetPagination({ current: 1, pageSize: 10, total: 0 });
- setFiles([]);
- setFilesSearch("");
- setFilesPagination({ current: 1, pageSize: 10, total: 0 });
- setShowFiles(false);
- setSelectedDataset(null);
- setDatasetSelections([]);
- onDatasetSelect?.(null);
- }
- }, [open, onDatasetSelect]);
-
- const datasetCols = [
- {
- title: "数据集名称",
- dataIndex: "name",
- key: "name",
- ellipsis: true,
- },
- {
- title: "文件数",
- dataIndex: "fileCount",
- key: "fileCount",
- ellipsis: true,
- },
- {
- title: "大小",
- dataIndex: "totalSize",
- key: "totalSize",
- ellipsis: true,
- render: formatBytes,
- },
- ];
-
- return (
-
-
-
-
选择数据集
-
- setDatasetSearch(e.target.value)}
- />
-
-
- selectedDataset?.id === record.id ? "bg-blue-100" : ""
- }
- onRow={(record: Dataset) => ({
- onClick: () => {
- setSelectedDataset(record);
- if (!datasetSelections.find((d) => d.id === record.id)) {
- setDatasetSelections([...datasetSelections, record]);
- } else {
- setDatasetSelections(
- datasetSelections.filter((d) => d.id !== record.id)
- );
- }
- },
- })}
- dataSource={datasets}
- columns={datasetCols}
- pagination={{
- ...datasetPagination,
- onChange: (page, pageSize) =>
- setDatasetPagination({
- current: page,
- pageSize: pageSize || datasetPagination.pageSize,
- total: datasetPagination.total,
- }),
- }}
- />
-
-
-
-
选择文件
-
- setFilesSearch(e.target.value)}
- />
-
-
{
- const nextPageSize = pageSize || filesPagination.pageSize;
- setFilesPagination((prev) => ({
- ...prev,
- current: page,
- pageSize: nextPageSize,
- }));
- fetchFiles({ page, pageSize: nextPageSize }).catch(() => {});
- },
- }}
- onRow={(record: DatasetFile) => ({
- onClick: () => toggleSelectFile(record),
- })}
- rowSelection={{
- type: "checkbox",
- selectedRowKeys: Object.keys(selectedFilesMap),
-
- // 单选
- onSelect: (record: DatasetFile) => {
- toggleSelectFile(record);
- },
-
- // 全选
- onSelectAll: (selected, selectedRows: DatasetFile[]) => {
- if (selected) {
- // ✔ 全选 -> 将 files 列表全部加入 selectedFilesMap
- const newMap: Record = { ...selectedFilesMap };
- selectedRows.forEach((f) => {
- newMap[f.id] = f;
- });
- onSelectedFilesChange(newMap);
- } else {
- // ✘ 取消全选 -> 清空 map
- const newMap = { ...selectedFilesMap };
- Object.keys(newMap).forEach((id) => {
- if (files.some((f) => String(f.id) === id)) {
- // 仅移除当前页对应文件
- delete newMap[id];
- }
- });
- onSelectedFilesChange(newMap);
- }
- },
-
- getCheckboxProps: (record: DatasetFile) => ({
- name: record.fileName,
- }),
- }}
- />
-
-
-
-
-
- );
-};
-
-export default DatasetFileTransfer;
+import React, { useCallback, useEffect } from "react";
+import { Button, Input, Table } from "antd";
+import { RightOutlined } from "@ant-design/icons";
+import { mapDataset } from "@/pages/DataManagement/dataset.const";
+import {
+ Dataset,
+ DatasetFile,
+ DatasetType,
+} from "@/pages/DataManagement/dataset.model";
+import {
+ queryDatasetFilesUsingGet,
+ queryDatasetsUsingGet,
+} from "@/pages/DataManagement/dataset.api";
+import { formatBytes } from "@/utils/unit";
+import { useDebouncedEffect } from "@/hooks/useDebouncedEffect";
+
+interface DatasetFileTransferProps
+ extends React.HTMLAttributes {
+ open: boolean;
+ selectedFilesMap: { [key: string]: DatasetFile };
+ onSelectedFilesChange: (filesMap: { [key: string]: DatasetFile }) => void;
+ onDatasetSelect?: (dataset: Dataset | null) => void;
+}
+
+const fileCols = [
+ {
+ title: "所属数据集",
+ dataIndex: "datasetName",
+ key: "datasetName",
+ ellipsis: true,
+ },
+ {
+ title: "文件名",
+ dataIndex: "fileName",
+ key: "fileName",
+ ellipsis: true,
+ },
+ {
+ title: "大小",
+ dataIndex: "fileSize",
+ key: "fileSize",
+ ellipsis: true,
+ render: formatBytes,
+ },
+];
+
+// Customize Table Transfer
+const DatasetFileTransfer: React.FC = ({
+ open,
+ selectedFilesMap,
+ onSelectedFilesChange,
+ onDatasetSelect,
+ ...props
+}) => {
+ const [datasets, setDatasets] = React.useState([]);
+ const [datasetSearch, setDatasetSearch] = React.useState("");
+ const [datasetPagination, setDatasetPagination] = React.useState<{
+ current: number;
+ pageSize: number;
+ total: number;
+ }>({ current: 1, pageSize: 10, total: 0 });
+
+ const [files, setFiles] = React.useState([]);
+ const [filesSearch, setFilesSearch] = React.useState("");
+ const [filesPagination, setFilesPagination] = React.useState<{
+ current: number;
+ pageSize: number;
+ total: number;
+ }>({ current: 1, pageSize: 10, total: 0 });
+
+ const [showFiles, setShowFiles] = React.useState(false);
+ const [selectedDataset, setSelectedDataset] = React.useState(
+ null
+ );
+ const [datasetSelections, setDatasetSelections] = React.useState(
+ []
+ );
+
+ const fetchDatasets = async () => {
+ const { data } = await queryDatasetsUsingGet({
+ // Ant Design Table pagination.current is 1-based; ensure backend also receives 1-based value
+ page: datasetPagination.current,
+ size: datasetPagination.pageSize,
+ keyword: datasetSearch,
+ type: DatasetType.TEXT,
+ });
+ setDatasets(data.content.map(mapDataset) || []);
+ setDatasetPagination((prev) => ({
+ ...prev,
+ total: data.totalElements,
+ }));
+ };
+
+ useDebouncedEffect(
+ () => {
+ fetchDatasets();
+ },
+ [datasetSearch, datasetPagination.pageSize, datasetPagination.current],
+ 300
+ );
+
+ const fetchFiles = useCallback(
+ async (
+ options?: Partial<{ page: number; pageSize: number; keyword: string }>
+ ) => {
+ if (!selectedDataset) return;
+ const page = options?.page ?? filesPagination.current;
+ const pageSize = options?.pageSize ?? filesPagination.pageSize;
+ const keyword = options?.keyword ?? filesSearch;
+
+ const { data } = await queryDatasetFilesUsingGet(selectedDataset.id, {
+ page,
+ size: pageSize,
+ keyword,
+ });
+ setFiles(
+ (data.content || []).map((item: DatasetFile) => ({
+ ...item,
+ key: item.id,
+ datasetName: selectedDataset.name,
+ }))
+ );
+ setFilesPagination((prev) => ({
+ ...prev,
+ current: page,
+ pageSize,
+ total: data.totalElements,
+ }));
+ },
+ [selectedDataset, filesPagination.current, filesPagination.pageSize, filesSearch]
+ );
+
+ useEffect(() => {
+ // 当数据集变化时,重置文件分页并拉取第一页文件,避免额外的循环请求
+ if (selectedDataset) {
+ setFilesPagination({ current: 1, pageSize: 10, total: 0 });
+ fetchFiles({ page: 1, pageSize: 10 }).catch(() => {});
+ } else {
+ setFiles([]);
+ setFilesPagination({ current: 1, pageSize: 10, total: 0 });
+ }
+ // 只在 selectedDataset 变化时触发
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [selectedDataset]);
+
+ useEffect(() => {
+ onDatasetSelect?.(selectedDataset);
+ }, [selectedDataset, onDatasetSelect]);
+
+ const toggleSelectFile = (record: DatasetFile) => {
+ if (!selectedFilesMap[record.id]) {
+ onSelectedFilesChange({
+ ...selectedFilesMap,
+ [record.id]: record,
+ });
+ } else {
+ const newSelectedFiles = { ...selectedFilesMap };
+ delete newSelectedFiles[record.id];
+ onSelectedFilesChange(newSelectedFiles);
+ }
+ };
+
+ useEffect(() => {
+ if (!open) {
+ // 重置状态
+ setDatasets([]);
+ setDatasetSearch("");
+ setDatasetPagination({ current: 1, pageSize: 10, total: 0 });
+ setFiles([]);
+ setFilesSearch("");
+ setFilesPagination({ current: 1, pageSize: 10, total: 0 });
+ setShowFiles(false);
+ setSelectedDataset(null);
+ setDatasetSelections([]);
+ onDatasetSelect?.(null);
+ }
+ }, [open, onDatasetSelect]);
+
+ const datasetCols = [
+ {
+ title: "数据集名称",
+ dataIndex: "name",
+ key: "name",
+ ellipsis: true,
+ },
+ {
+ title: "文件数",
+ dataIndex: "fileCount",
+ key: "fileCount",
+ ellipsis: true,
+ },
+ {
+ title: "大小",
+ dataIndex: "totalSize",
+ key: "totalSize",
+ ellipsis: true,
+ render: formatBytes,
+ },
+ ];
+
+ return (
+
+
+
+
选择数据集
+
+ setDatasetSearch(e.target.value)}
+ />
+
+
+ selectedDataset?.id === record.id ? "bg-blue-100" : ""
+ }
+ onRow={(record: Dataset) => ({
+ onClick: () => {
+ setSelectedDataset(record);
+ if (!datasetSelections.find((d) => d.id === record.id)) {
+ setDatasetSelections([...datasetSelections, record]);
+ } else {
+ setDatasetSelections(
+ datasetSelections.filter((d) => d.id !== record.id)
+ );
+ }
+ },
+ })}
+ dataSource={datasets}
+ columns={datasetCols}
+ pagination={{
+ ...datasetPagination,
+ onChange: (page, pageSize) =>
+ setDatasetPagination({
+ current: page,
+ pageSize: pageSize || datasetPagination.pageSize,
+ total: datasetPagination.total,
+ }),
+ }}
+ />
+
+
+
+
选择文件
+
+ setFilesSearch(e.target.value)}
+ />
+
+
{
+ const nextPageSize = pageSize || filesPagination.pageSize;
+ setFilesPagination((prev) => ({
+ ...prev,
+ current: page,
+ pageSize: nextPageSize,
+ }));
+ fetchFiles({ page, pageSize: nextPageSize }).catch(() => {});
+ },
+ }}
+ onRow={(record: DatasetFile) => ({
+ onClick: () => toggleSelectFile(record),
+ })}
+ rowSelection={{
+ type: "checkbox",
+ selectedRowKeys: Object.keys(selectedFilesMap),
+
+ // 单选
+ onSelect: (record: DatasetFile) => {
+ toggleSelectFile(record);
+ },
+
+ // 全选
+ onSelectAll: (selected, selectedRows: DatasetFile[]) => {
+ if (selected) {
+ // ✔ 全选 -> 将 files 列表全部加入 selectedFilesMap
+ const newMap: Record = { ...selectedFilesMap };
+ selectedRows.forEach((f) => {
+ newMap[f.id] = f;
+ });
+ onSelectedFilesChange(newMap);
+ } else {
+ // ✘ 取消全选 -> 清空 map
+ const newMap = { ...selectedFilesMap };
+ Object.keys(newMap).forEach((id) => {
+ if (files.some((f) => String(f.id) === id)) {
+ // 仅移除当前页对应文件
+ delete newMap[id];
+ }
+ });
+ onSelectedFilesChange(newMap);
+ }
+ },
+
+ getCheckboxProps: (record: DatasetFile) => ({
+ name: record.fileName,
+ }),
+ }}
+ />
+
+
+
+
+
+ );
+};
+
+export default DatasetFileTransfer;
diff --git a/frontend/src/components/business/TagManagement.tsx b/frontend/src/components/business/TagManagement.tsx
index bf3c45786..42913b372 100644
--- a/frontend/src/components/business/TagManagement.tsx
+++ b/frontend/src/components/business/TagManagement.tsx
@@ -1,251 +1,251 @@
-import React, { useEffect, useState } from "react";
-import { Drawer, Input, Button, App } from "antd";
-import { PlusOutlined } from "@ant-design/icons";
-import { Edit, Save, TagIcon, X, Trash } from "lucide-react";
-import { TagItem } from "@/pages/DataManagement/dataset.model";
-
-interface CustomTagProps {
- isEditable?: boolean;
- tag: { id: number; name: string };
- editingTag?: string | null;
- editingTagValue?: string;
- setEditingTag?: React.Dispatch>;
- setEditingTagValue?: React.Dispatch>;
- handleEditTag?: (tag: { id: number; name: string }, value: string) => void;
- handleCancelEdit?: (tag: { id: number; name: string }) => void;
- handleDeleteTag?: (tag: { id: number; name: string }) => void;
-}
-
-function CustomTag({
- isEditable = false,
- tag,
- editingTag,
- editingTagValue,
- setEditingTag,
- setEditingTagValue,
- handleEditTag,
- handleCancelEdit,
- handleDeleteTag,
-}: CustomTagProps) {
- return (
-
- {editingTag?.id === tag.id ? (
-
- setEditingTagValue?.(e.target.value)}
- onKeyPress={(e) => {
- if (e.key === "Enter") {
- handleEditTag?.(tag, editingTagValue);
- }
- if (e.key === "Escape") {
- setEditingTag?.(null);
- setEditingTagValue?.("");
- }
- }}
- className="h-6 text-sm"
- autoFocus
- />
-
- ) : (
- <>
-
{tag.name}
- {isEditable && (
-
- {
- setEditingTag?.(tag);
- setEditingTagValue?.(tag.name);
- }}
- icon={}
- />
- handleDeleteTag?.(tag)}
- icon={}
- />
-
- )}
- >
- )}
-
- );
-}
-
-const TagManager: React.FC = ({
- onFetch,
- onCreate,
- onDelete,
- onUpdate,
-}: {
- onFetch: () => Promise;
- onCreate: (tag: Pick) => Promise<{ ok: boolean }>;
- onDelete: (tagId: number) => Promise<{ ok: boolean }>;
- onUpdate: (tag: TagItem) => Promise<{ ok: boolean }>;
-}) => {
- const [showTagManager, setShowTagManager] = useState(false);
- const { message } = App.useApp();
- const [tags, setTags] = useState<{ id: number; name: string }[]>([]);
- const [newTag, setNewTag] = useState("");
- const [editingTag, setEditingTag] = useState(null);
- const [editingTagValue, setEditingTagValue] = useState("");
-
- // 获取标签列表
- const fetchTags = async () => {
- if (!onFetch) return;
- try {
- const { data } = await onFetch?.();
- setTags(data || []);
- } catch (e) {
- message.error("获取标签失败");
- }
- };
-
- // 添加标签
- const addTag = async (tag: string) => {
- try {
- await onCreate?.({
- name: tag,
- });
- fetchTags();
- setNewTag("");
- message.success("标签添加成功");
- } catch (error) {
- message.error("添加标签失败");
- }
- };
-
- // 删除标签
- const deleteTag = async (tag: TagItem) => {
- try {
- await onDelete?.(tag.id);
- fetchTags();
- message.success("标签删除成功");
- } catch (error) {
- message.error("删除标签失败");
- }
- };
-
- const updateTag = async (oldTag: TagItem, newTag: string) => {
- try {
- await onUpdate?.({ ...oldTag, name: newTag });
- fetchTags();
- message.success("标签更新成功");
- } catch (error) {
- message.error("更新标签失败");
- }
- };
-
- const handleCreateNewTag = () => {
- if (newTag.trim()) {
- addTag(newTag.trim());
- setNewTag("");
- }
- };
-
- const handleEditTag = (tag: TagItem, value: string) => {
- if (value.trim()) {
- updateTag(tag, value.trim());
- setEditingTag(null);
- setEditingTagValue("");
- }
- };
-
- const handleCancelEdit = (tag: string) => {
- setEditingTag(null);
- setEditingTagValue("");
- };
-
- const handleDeleteTag = (tag: TagItem) => {
- deleteTag(tag);
- setEditingTag(null);
- setEditingTagValue("");
- };
-
- useEffect(() => {
- if (showTagManager) fetchTags();
- }, [showTagManager]);
-
- return (
- <>
- }
- onClick={() => setShowTagManager(true)}
- >
- 标签管理
-
- setShowTagManager(false)}
- title="标签管理"
- width={500}
- >
-
- {/* Add New Tag */}
-
- setNewTag(e.target.value)}
- onKeyPress={(e) => {
- if (e.key === "Enter") {
- addTag(e.target.value);
- }
- }}
- />
- }
- >
- 添加
-
-
-
-
-
- {tags.map((tag) => (
-
- ))}
-
-
-
-
- >
- );
-};
-
-export default TagManager;
+import React, { useEffect, useState } from "react";
+import { Drawer, Input, Button, App } from "antd";
+import { PlusOutlined } from "@ant-design/icons";
+import { Edit, Save, TagIcon, X, Trash } from "lucide-react";
+import { TagItem } from "@/pages/DataManagement/dataset.model";
+
+interface CustomTagProps {
+ isEditable?: boolean;
+ tag: { id: number; name: string };
+ editingTag?: string | null;
+ editingTagValue?: string;
+ setEditingTag?: React.Dispatch>;
+ setEditingTagValue?: React.Dispatch>;
+ handleEditTag?: (tag: { id: number; name: string }, value: string) => void;
+ handleCancelEdit?: (tag: { id: number; name: string }) => void;
+ handleDeleteTag?: (tag: { id: number; name: string }) => void;
+}
+
+function CustomTag({
+ isEditable = false,
+ tag,
+ editingTag,
+ editingTagValue,
+ setEditingTag,
+ setEditingTagValue,
+ handleEditTag,
+ handleCancelEdit,
+ handleDeleteTag,
+}: CustomTagProps) {
+ return (
+
+ {editingTag?.id === tag.id ? (
+
+ setEditingTagValue?.(e.target.value)}
+ onKeyPress={(e) => {
+ if (e.key === "Enter") {
+ handleEditTag?.(tag, editingTagValue);
+ }
+ if (e.key === "Escape") {
+ setEditingTag?.(null);
+ setEditingTagValue?.("");
+ }
+ }}
+ className="h-6 text-sm"
+ autoFocus
+ />
+ handleEditTag(tag, editingTagValue)}
+ type="link"
+ size="small"
+ icon={}
+ />
+ handleCancelEdit?.(tag)}
+ icon={}
+ />
+
+ ) : (
+ <>
+
{tag.name}
+ {isEditable && (
+
+ {
+ setEditingTag?.(tag);
+ setEditingTagValue?.(tag.name);
+ }}
+ icon={}
+ />
+ handleDeleteTag?.(tag)}
+ icon={}
+ />
+
+ )}
+ >
+ )}
+
+ );
+}
+
+const TagManager: React.FC = ({
+ onFetch,
+ onCreate,
+ onDelete,
+ onUpdate,
+}: {
+ onFetch: () => Promise;
+ onCreate: (tag: Pick) => Promise<{ ok: boolean }>;
+ onDelete: (tagId: number) => Promise<{ ok: boolean }>;
+ onUpdate: (tag: TagItem) => Promise<{ ok: boolean }>;
+}) => {
+ const [showTagManager, setShowTagManager] = useState(false);
+ const { message } = App.useApp();
+ const [tags, setTags] = useState<{ id: number; name: string }[]>([]);
+ const [newTag, setNewTag] = useState("");
+ const [editingTag, setEditingTag] = useState(null);
+ const [editingTagValue, setEditingTagValue] = useState("");
+
+ // 获取标签列表
+ const fetchTags = async () => {
+ if (!onFetch) return;
+ try {
+ const { data } = await onFetch?.();
+ setTags(data || []);
+ } catch (e) {
+ message.error("获取标签失败");
+ }
+ };
+
+ // 添加标签
+ const addTag = async (tag: string) => {
+ try {
+ await onCreate?.({
+ name: tag,
+ });
+ fetchTags();
+ setNewTag("");
+ message.success("标签添加成功");
+ } catch (error) {
+ message.error("添加标签失败");
+ }
+ };
+
+ // 删除标签
+ const deleteTag = async (tag: TagItem) => {
+ try {
+ await onDelete?.(tag.id);
+ fetchTags();
+ message.success("标签删除成功");
+ } catch (error) {
+ message.error("删除标签失败");
+ }
+ };
+
+ const updateTag = async (oldTag: TagItem, newTag: string) => {
+ try {
+ await onUpdate?.({ ...oldTag, name: newTag });
+ fetchTags();
+ message.success("标签更新成功");
+ } catch (error) {
+ message.error("更新标签失败");
+ }
+ };
+
+ const handleCreateNewTag = () => {
+ if (newTag.trim()) {
+ addTag(newTag.trim());
+ setNewTag("");
+ }
+ };
+
+ const handleEditTag = (tag: TagItem, value: string) => {
+ if (value.trim()) {
+ updateTag(tag, value.trim());
+ setEditingTag(null);
+ setEditingTagValue("");
+ }
+ };
+
+ const handleCancelEdit = (tag: string) => {
+ setEditingTag(null);
+ setEditingTagValue("");
+ };
+
+ const handleDeleteTag = (tag: TagItem) => {
+ deleteTag(tag);
+ setEditingTag(null);
+ setEditingTagValue("");
+ };
+
+ useEffect(() => {
+ if (showTagManager) fetchTags();
+ }, [showTagManager]);
+
+ return (
+ <>
+ }
+ onClick={() => setShowTagManager(true)}
+ >
+ 标签管理
+
+ setShowTagManager(false)}
+ title="标签管理"
+ width={500}
+ >
+
+ {/* Add New Tag */}
+
+ setNewTag(e.target.value)}
+ onKeyPress={(e) => {
+ if (e.key === "Enter") {
+ addTag(e.target.value);
+ }
+ }}
+ />
+ }
+ >
+ 添加
+
+
+
+
+
+ {tags.map((tag) => (
+
+ ))}
+
+
+
+
+ >
+ );
+};
+
+export default TagManager;
diff --git a/frontend/src/components/business/TaskPopover.tsx b/frontend/src/components/business/TaskPopover.tsx
index e1b838abd..2a0e7467e 100644
--- a/frontend/src/components/business/TaskPopover.tsx
+++ b/frontend/src/components/business/TaskPopover.tsx
@@ -1,162 +1,162 @@
-import { Button, Popover, Progress } from "antd";
-import { Calendar, Clock, Play, Trash2, X } from "lucide-react";
-
-interface TaskItem {
- id: string;
- name: string;
- status: string;
- progress: number;
- scheduleConfig: {
- type: string;
- cronExpression?: string;
- executionCount?: number;
- maxExecutions?: number;
- };
- nextExecution?: string;
- importConfig: {
- source?: string;
- };
- createdAt: string;
-}
-
-export default function TaskPopover() {
- const tasks: TaskItem[] = [
- {
- id: "1",
- name: "导入客户数据",
- status: "importing",
- progress: 65,
- scheduleConfig: {
- type: "manual",
- },
- importConfig: {
- source: "local",
- },
- createdAt: "2025-07-29 14:23",
- },
- {
- id: "2",
- name: "定时同步订单",
- status: "waiting",
- progress: 0,
- scheduleConfig: {
- type: "scheduled",
- cronExpression: "0 0 * * *",
- executionCount: 3,
- maxExecutions: 10,
- },
- nextExecution: "2025-07-31 00:00",
- importConfig: {
- source: "api",
- },
- createdAt: "2025-07-28 09:10",
- },
- {
- id: "3",
- name: "清理历史日志",
- status: "finished",
- progress: 100,
- scheduleConfig: {
- type: "manual",
- },
- importConfig: {
- source: "system",
- },
- createdAt: "2025-07-27 17:45",
- },
- ];
-
- return (
-
-
-
近期任务
-
-
-
-
-
-
- {tasks.length === 0 ? (
-
- ) : (
-
- {tasks.map((task) => (
-
-
-
-
- {task.name}
-
-
- {task.status === "waiting" && (
-
-
-
- )}
-
-
-
-
-
-
- {task.status === "importing" && (
-
-
- 导入进度
- {Math.round(task.progress)}%
-
-
-
- )}
-
- {/* Schedule Information */}
- {task.scheduleConfig.type === "scheduled" && (
-
-
-
- 定时任务
-
-
Cron: {task.scheduleConfig.cronExpression}
- {task.nextExecution && (
-
下次执行: {task.nextExecution}
- )}
-
- 执行次数: {task.scheduleConfig.executionCount || 0}/
- {task.scheduleConfig.maxExecutions || 10}
-
-
- )}
-
-
-
- {task.importConfig.source === "local"
- ? "本地上传"
- : task.importConfig.source || "未知来源"}
-
- {task.createdAt}
-
-
-
- ))}
-
- )}
-
-
- }
- >
- 任务中心
-
- );
-}
+import { Button, Popover, Progress } from "antd";
+import { Calendar, Clock, Play, Trash2, X } from "lucide-react";
+
+interface TaskItem {
+ id: string;
+ name: string;
+ status: string;
+ progress: number;
+ scheduleConfig: {
+ type: string;
+ cronExpression?: string;
+ executionCount?: number;
+ maxExecutions?: number;
+ };
+ nextExecution?: string;
+ importConfig: {
+ source?: string;
+ };
+ createdAt: string;
+}
+
+export default function TaskPopover() {
+ const tasks: TaskItem[] = [
+ {
+ id: "1",
+ name: "导入客户数据",
+ status: "importing",
+ progress: 65,
+ scheduleConfig: {
+ type: "manual",
+ },
+ importConfig: {
+ source: "local",
+ },
+ createdAt: "2025-07-29 14:23",
+ },
+ {
+ id: "2",
+ name: "定时同步订单",
+ status: "waiting",
+ progress: 0,
+ scheduleConfig: {
+ type: "scheduled",
+ cronExpression: "0 0 * * *",
+ executionCount: 3,
+ maxExecutions: 10,
+ },
+ nextExecution: "2025-07-31 00:00",
+ importConfig: {
+ source: "api",
+ },
+ createdAt: "2025-07-28 09:10",
+ },
+ {
+ id: "3",
+ name: "清理历史日志",
+ status: "finished",
+ progress: 100,
+ scheduleConfig: {
+ type: "manual",
+ },
+ importConfig: {
+ source: "system",
+ },
+ createdAt: "2025-07-27 17:45",
+ },
+ ];
+
+ return (
+
+
+
近期任务
+
+
+
+
+
+
+ {tasks.length === 0 ? (
+
+ ) : (
+
+ {tasks.map((task) => (
+
+
+
+
+ {task.name}
+
+
+ {task.status === "waiting" && (
+
+
+
+ )}
+
+
+
+
+
+
+ {task.status === "importing" && (
+
+
+ 导入进度
+ {Math.round(task.progress)}%
+
+
+
+ )}
+
+ {/* Schedule Information */}
+ {task.scheduleConfig.type === "scheduled" && (
+
+
+
+ 定时任务
+
+
Cron: {task.scheduleConfig.cronExpression}
+ {task.nextExecution && (
+
下次执行: {task.nextExecution}
+ )}
+
+ 执行次数: {task.scheduleConfig.executionCount || 0}/
+ {task.scheduleConfig.maxExecutions || 10}
+
+
+ )}
+
+
+
+ {task.importConfig.source === "local"
+ ? "本地上传"
+ : task.importConfig.source || "未知来源"}
+
+ {task.createdAt}
+
+
+
+ ))}
+
+ )}
+
+
+ }
+ >
+ 任务中心
+
+ );
+}
diff --git a/frontend/src/hooks/useDebouncedEffect.ts b/frontend/src/hooks/useDebouncedEffect.ts
index 152128560..dfd56aac3 100644
--- a/frontend/src/hooks/useDebouncedEffect.ts
+++ b/frontend/src/hooks/useDebouncedEffect.ts
@@ -1,17 +1,17 @@
-import { useEffect } from "react";
-
-export function useDebouncedEffect(
- cb: () => void,
- deps: any[] = [],
- delay: number = 300
-) {
- useEffect(() => {
- const handler = setTimeout(() => {
- cb();
- }, delay);
-
- return () => {
- clearTimeout(handler);
- };
- }, [...(deps || []), delay]);
-}
+import { useEffect } from "react";
+
+export function useDebouncedEffect(
+ cb: () => void,
+ deps: any[] = [],
+ delay: number = 300
+) {
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ cb();
+ }, delay);
+
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [...(deps || []), delay]);
+}
diff --git a/frontend/src/hooks/useFetchData.ts b/frontend/src/hooks/useFetchData.ts
index c3319c9e2..7df77384a 100644
--- a/frontend/src/hooks/useFetchData.ts
+++ b/frontend/src/hooks/useFetchData.ts
@@ -1,238 +1,236 @@
-// 首页数据获取
-// 支持轮询功能,使用示例:
-// const { fetchData, startPolling, stopPolling, isPolling } = useFetchData(
-// fetchFunction,
-// mapFunction,
-// 5000, // 5秒轮询一次,默认30秒
-// true, // 是否自动开始轮询,默认 true
-// [fetchStatistics, fetchOtherData] // 额外的轮询函数数组
-// );
-//
-// startPolling(); // 开始轮询
-// stopPolling(); // 停止轮询
-// 手动调用 fetchData() 时,如果正在轮询,会重新开始轮询计时
-// 轮询时会同时执行主要的 fetchFunction 和所有额外的轮询函数
-import { useState, useRef, useEffect, useCallback } from "react";
-import { useDebouncedEffect } from "./useDebouncedEffect";
-import Loading from "@/utils/loading";
-import { App } from "antd";
-
-export default function useFetchData(
- fetchFunc: (params?: any) => Promise,
- mapDataFunc: (data: Partial) => T = (data) => data as T,
- pollingInterval: number = 30000, // 默认30秒轮询一次
- autoRefresh: boolean = false, // 是否自动开始轮询,默认 false
- additionalPollingFuncs: (() => Promise)[] = [], // 额外的轮询函数
- pageOffset: number = 1
-) {
- const { message } = App.useApp();
-
- // 轮询相关状态
- const [isPolling, setIsPolling] = useState(false);
- const pollingTimerRef = useRef(null);
-
- // 表格数据
- const [tableData, setTableData] = useState([]);
- // 设置加载状态
- const [loading, setLoading] = useState(false);
-
- // 搜索参数
- const [searchParams, setSearchParams] = useState({
- keyword: "",
- filter: {
- type: [] as string[],
- status: [] as string[],
- tags: [] as string[],
- // 通用分类筛选(如算子市场的分类 ID 列表)
- categories: [] as string[],
- },
- current: 1,
- pageSize: 12,
- });
-
- // 分页配置
- const [pagination, setPagination] = useState({
- total: 0,
- showSizeChanger: true,
- pageSizeOptions: ["12", "24", "48"],
- showTotal: (total: number) => `共 ${total} 条`,
- onChange: (current: number, pageSize?: number) => {
- setSearchParams((prev) => ({
- ...prev,
- current,
- pageSize: pageSize || prev.pageSize,
- }));
- },
- });
-
- const handleFiltersChange = (searchFilters: { [key: string]: string[] }) => {
- setSearchParams({
- ...searchParams,
- current: 1,
- filter: { ...searchParams.filter, ...searchFilters },
- });
- };
-
- const handleKeywordChange = (keyword: string) => {
- setSearchParams({
- ...searchParams,
- current: 1,
- keyword: keyword,
- });
- };
-
- function getFirstOfArray(arr: string[]) {
- if (!arr || arr.length === 0 || !Array.isArray(arr)) return undefined;
- if (arr[0] === "all") return undefined;
- return arr[0];
- }
-
- // 清除轮询定时器
- const clearPollingTimer = useCallback(() => {
- if (pollingTimerRef.current) {
- clearTimeout(pollingTimerRef.current);
- pollingTimerRef.current = null;
- }
- }, []);
-
- const fetchData = useCallback(
- async (extraParams = {}, skipPollingRestart = false) => {
- const { keyword, filter, current, pageSize } = searchParams;
- if (!skipPollingRestart) {
- Loading.show();
- setLoading(true);
- }
-
- // 如果正在轮询且不是轮询触发的调用,先停止当前轮询
- const wasPolling = isPolling && !skipPollingRestart;
- if (wasPolling) {
- clearPollingTimer();
- }
-
- try {
- // 同时执行主要数据获取和额外的轮询函数
- const promises = [
- fetchFunc({
- ...Object.fromEntries(
- Object.entries(filter).filter(([_, value]) => value != null && value.length > 0)
- ),
- ...extraParams,
- keyword,
- type: getFirstOfArray(filter?.type) || undefined,
- status: getFirstOfArray(filter?.status) || undefined,
- tags: filter?.tags?.length ? filter.tags.join(",") : undefined,
- page: current - pageOffset,
- size: pageSize, // Use camelCase for HTTP query params
- }),
- ...additionalPollingFuncs.map((func) => func()),
- ];
-
- const results = await Promise.all(promises);
- const { data } = results[0]; // 主要数据结果
-
- setPagination((prev) => ({
- ...prev,
- total: data?.totalElements || 0,
- }));
- let result = [];
- if (mapDataFunc) {
- result = data?.content.map(mapDataFunc) ?? [];
- }
- setTableData(result);
-
- // 如果之前正在轮询且不是轮询触发的调用,重新开始轮询
- if (wasPolling) {
- const poll = () => {
- pollingTimerRef.current = setTimeout(() => {
- fetchData({}, true).then(() => {
- if (pollingTimerRef.current) {
- poll();
- }
- });
- }, pollingInterval);
- };
- poll();
- }
- } catch (error) {
- console.error(error);
- message.error("数据获取失败,请稍后重试");
- } finally {
- Loading.hide();
- setLoading(false);
- }
- },
- [
- searchParams,
- fetchFunc,
- mapDataFunc,
- isPolling,
- clearPollingTimer,
- pollingInterval,
- message,
- additionalPollingFuncs,
- ]
- );
-
- // 开始轮询
- const startPolling = useCallback(() => {
- clearPollingTimer();
- setIsPolling(true);
-
- const poll = () => {
- pollingTimerRef.current = setTimeout(() => {
- fetchData({}, true).then(() => {
- if (pollingTimerRef.current) {
- poll();
- }
- });
- }, pollingInterval);
- };
-
- poll();
- }, [pollingInterval, clearPollingTimer, fetchData]);
-
- // 停止轮询
- const stopPolling = useCallback(() => {
- clearPollingTimer();
- setIsPolling(false);
- }, [clearPollingTimer]);
-
- // 搜索参数变化时,自动刷新数据
- // keyword 变化时,防抖500ms后刷新
- useDebouncedEffect(
- () => {
- fetchData();
- },
- [searchParams],
- searchParams?.keyword ? 500 : 0
- );
-
- // 组件卸载时清理轮询
- useEffect(() => {
- if (autoRefresh) {
- startPolling();
- }
- return () => {
- clearPollingTimer();
- };
- }, [clearPollingTimer]);
-
- return {
- loading,
- tableData,
- pagination: {
- ...pagination,
- current: searchParams.current,
- pageSize: searchParams.pageSize,
- },
- searchParams,
- setSearchParams,
- setPagination,
- handleFiltersChange,
- handleKeywordChange,
- fetchData,
- isPolling,
- startPolling,
- stopPolling,
- };
-}
+// 首页数据获取
+// 支持轮询功能,使用示例:
+// const { fetchData, startPolling, stopPolling, isPolling } = useFetchData(
+// fetchFunction,
+// mapFunction,
+// 5000, // 5秒轮询一次,默认30秒
+// true, // 是否自动开始轮询,默认 true
+// [fetchStatistics, fetchOtherData] // 额外的轮询函数数组
+// );
+//
+// startPolling(); // 开始轮询
+// stopPolling(); // 停止轮询
+// 手动调用 fetchData() 时,如果正在轮询,会重新开始轮询计时
+// 轮询时会同时执行主要的 fetchFunction 和所有额外的轮询函数
+import { useState, useRef, useEffect, useCallback } from "react";
+import { useDebouncedEffect } from "./useDebouncedEffect";
+import Loading from "@/utils/loading";
+import { App } from "antd";
+
+export default function useFetchData(
+ fetchFunc: (params?: any) => Promise,
+ mapDataFunc: (data: Partial) => T = (data) => data as T,
+ pollingInterval: number = 30000, // 默认30秒轮询一次
+ autoRefresh: boolean = false, // 是否自动开始轮询,默认 false
+ additionalPollingFuncs: (() => Promise)[] = [], // 额外的轮询函数
+ pageOffset: number = 1
+) {
+ const { message } = App.useApp();
+
+ // 轮询相关状态
+ const [isPolling, setIsPolling] = useState(false);
+ const pollingTimerRef = useRef(null);
+
+ // 表格数据
+ const [tableData, setTableData] = useState([]);
+ // 设置加载状态
+ const [loading, setLoading] = useState(false);
+
+ // 搜索参数
+ const [searchParams, setSearchParams] = useState({
+ keyword: "",
+ filter: {
+ type: [] as string[],
+ status: [] as string[],
+ tags: [] as string[],
+ },
+ current: 1,
+ pageSize: 12,
+ });
+
+ // 分页配置
+ const [pagination, setPagination] = useState({
+ total: 0,
+ showSizeChanger: true,
+ pageSizeOptions: ["12", "24", "48"],
+ showTotal: (total: number) => `共 ${total} 条`,
+ onChange: (current: number, pageSize?: number) => {
+ setSearchParams((prev) => ({
+ ...prev,
+ current,
+ pageSize: pageSize || prev.pageSize,
+ }));
+ },
+ });
+
+ const handleFiltersChange = (searchFilters: { [key: string]: string[] }) => {
+ setSearchParams({
+ ...searchParams,
+ current: 1,
+ filter: { ...searchParams.filter, ...searchFilters },
+ });
+ };
+
+ const handleKeywordChange = (keyword: string) => {
+ setSearchParams({
+ ...searchParams,
+ current: 1,
+ keyword: keyword,
+ });
+ };
+
+ function getFirstOfArray(arr: string[]) {
+ if (!arr || arr.length === 0 || !Array.isArray(arr)) return undefined;
+ if (arr[0] === "all") return undefined;
+ return arr[0];
+ }
+
+ // 清除轮询定时器
+ const clearPollingTimer = useCallback(() => {
+ if (pollingTimerRef.current) {
+ clearTimeout(pollingTimerRef.current);
+ pollingTimerRef.current = null;
+ }
+ }, []);
+
+ const fetchData = useCallback(
+ async (extraParams = {}, skipPollingRestart = false) => {
+ const { keyword, filter, current, pageSize } = searchParams;
+ if (!skipPollingRestart) {
+ Loading.show();
+ setLoading(true);
+ }
+
+ // 如果正在轮询且不是轮询触发的调用,先停止当前轮询
+ const wasPolling = isPolling && !skipPollingRestart;
+ if (wasPolling) {
+ clearPollingTimer();
+ }
+
+ try {
+ // 同时执行主要数据获取和额外的轮询函数
+ const promises = [
+ fetchFunc({
+ ...Object.fromEntries(
+ Object.entries(filter).filter(([_, value]) => value != null && value.length > 0)
+ ),
+ ...extraParams,
+ keyword,
+ type: getFirstOfArray(filter?.type) || undefined,
+ status: getFirstOfArray(filter?.status) || undefined,
+ tags: filter?.tags?.length ? filter.tags.join(",") : undefined,
+ page: current - pageOffset,
+ size: pageSize, // Use camelCase for HTTP query params
+ }),
+ ...additionalPollingFuncs.map((func) => func()),
+ ];
+
+ const results = await Promise.all(promises);
+ const { data } = results[0]; // 主要数据结果
+
+ setPagination((prev) => ({
+ ...prev,
+ total: data?.totalElements || 0,
+ }));
+ let result = [];
+ if (mapDataFunc) {
+ result = data?.content.map(mapDataFunc) ?? [];
+ }
+ setTableData(result);
+
+ // 如果之前正在轮询且不是轮询触发的调用,重新开始轮询
+ if (wasPolling) {
+ const poll = () => {
+ pollingTimerRef.current = setTimeout(() => {
+ fetchData({}, true).then(() => {
+ if (pollingTimerRef.current) {
+ poll();
+ }
+ });
+ }, pollingInterval);
+ };
+ poll();
+ }
+ } catch (error) {
+ console.error(error);
+ message.error("数据获取失败,请稍后重试");
+ } finally {
+ Loading.hide();
+ setLoading(false);
+ }
+ },
+ [
+ searchParams,
+ fetchFunc,
+ mapDataFunc,
+ isPolling,
+ clearPollingTimer,
+ pollingInterval,
+ message,
+ additionalPollingFuncs,
+ ]
+ );
+
+ // 开始轮询
+ const startPolling = useCallback(() => {
+ clearPollingTimer();
+ setIsPolling(true);
+
+ const poll = () => {
+ pollingTimerRef.current = setTimeout(() => {
+ fetchData({}, true).then(() => {
+ if (pollingTimerRef.current) {
+ poll();
+ }
+ });
+ }, pollingInterval);
+ };
+
+ poll();
+ }, [pollingInterval, clearPollingTimer, fetchData]);
+
+ // 停止轮询
+ const stopPolling = useCallback(() => {
+ clearPollingTimer();
+ setIsPolling(false);
+ }, [clearPollingTimer]);
+
+ // 搜索参数变化时,自动刷新数据
+ // keyword 变化时,防抖500ms后刷新
+ useDebouncedEffect(
+ () => {
+ fetchData();
+ },
+ [searchParams],
+ searchParams?.keyword ? 500 : 0
+ );
+
+ // 组件卸载时清理轮询
+ useEffect(() => {
+ if (autoRefresh) {
+ startPolling();
+ }
+ return () => {
+ clearPollingTimer();
+ };
+ }, [clearPollingTimer]);
+
+ return {
+ loading,
+ tableData,
+ pagination: {
+ ...pagination,
+ current: searchParams.current,
+ pageSize: searchParams.pageSize,
+ },
+ searchParams,
+ setSearchParams,
+ setPagination,
+ handleFiltersChange,
+ handleKeywordChange,
+ fetchData,
+ isPolling,
+ startPolling,
+ stopPolling,
+ };
+}
diff --git a/frontend/src/hooks/useLeavePrompt.ts b/frontend/src/hooks/useLeavePrompt.ts
index 57bbbf569..b7d083160 100644
--- a/frontend/src/hooks/useLeavePrompt.ts
+++ b/frontend/src/hooks/useLeavePrompt.ts
@@ -1,52 +1,52 @@
-import { useCallback, useEffect, useState } from "react";
-import { useNavigate } from "react-router";
-
-// 自定义hook:页面离开前提示
-export function useLeavePrompt(shouldPrompt: boolean) {
- const navigate = useNavigate();
- const [showPrompt, setShowPrompt] = useState(false);
- const [nextPath, setNextPath] = useState(null);
-
- // 浏览器刷新/关闭
- useEffect(() => {
- const handler = (e: BeforeUnloadEvent) => {
- if (shouldPrompt) {
- e.preventDefault();
- e.returnValue = "";
- return "";
- }
- };
- window.addEventListener("beforeunload", handler);
- return () => window.removeEventListener("beforeunload", handler);
- }, [shouldPrompt]);
-
- // 路由切换拦截
- useEffect(() => {
- const unblock = (window as any).__REACT_ROUTER_DOM_HISTORY__?.block?.(
- (tx: any) => {
- if (shouldPrompt) {
- setShowPrompt(true);
- setNextPath(tx.location.pathname);
- return false;
- }
- return true;
- }
- );
- return () => {
- if (unblock) unblock();
- };
- }, [shouldPrompt]);
-
- const confirmLeave = useCallback(() => {
- setShowPrompt(false);
- if (nextPath) {
- navigate(nextPath, { replace: true });
- }
- }, [nextPath, navigate]);
-
- return {
- showPrompt,
- setShowPrompt,
- confirmLeave,
- };
-}
+import { useCallback, useEffect, useState } from "react";
+import { useNavigate } from "react-router";
+
+// 自定义hook:页面离开前提示
+export function useLeavePrompt(shouldPrompt: boolean) {
+ const navigate = useNavigate();
+ const [showPrompt, setShowPrompt] = useState(false);
+ const [nextPath, setNextPath] = useState(null);
+
+ // 浏览器刷新/关闭
+ useEffect(() => {
+ const handler = (e: BeforeUnloadEvent) => {
+ if (shouldPrompt) {
+ e.preventDefault();
+ e.returnValue = "";
+ return "";
+ }
+ };
+ window.addEventListener("beforeunload", handler);
+ return () => window.removeEventListener("beforeunload", handler);
+ }, [shouldPrompt]);
+
+ // 路由切换拦截
+ useEffect(() => {
+ const unblock = (window as any).__REACT_ROUTER_DOM_HISTORY__?.block?.(
+ (tx: any) => {
+ if (shouldPrompt) {
+ setShowPrompt(true);
+ setNextPath(tx.location.pathname);
+ return false;
+ }
+ return true;
+ }
+ );
+ return () => {
+ if (unblock) unblock();
+ };
+ }, [shouldPrompt]);
+
+ const confirmLeave = useCallback(() => {
+ setShowPrompt(false);
+ if (nextPath) {
+ navigate(nextPath, { replace: true });
+ }
+ }, [nextPath, navigate]);
+
+ return {
+ showPrompt,
+ setShowPrompt,
+ confirmLeave,
+ };
+}
diff --git a/frontend/src/hooks/useSearchParams.tsx b/frontend/src/hooks/useSearchParams.tsx
index e02f8c64b..3514d0a94 100644
--- a/frontend/src/hooks/useSearchParams.tsx
+++ b/frontend/src/hooks/useSearchParams.tsx
@@ -1,18 +1,18 @@
-import { useMemo } from "react";
-import { useLocation } from "react-router";
-
-interface AnyObject {
- [key: string]: any;
-}
-
-export function useSearchParams(): AnyObject {
- const { search } = useLocation();
- return useMemo(() => {
- const urlParams = new URLSearchParams(search);
- const params: AnyObject = {};
- for (const [key, value] of urlParams.entries()) {
- params[key] = value;
- }
- return params;
- }, [search]);
-}
+import { useMemo } from "react";
+import { useLocation } from "react-router";
+
+interface AnyObject {
+ [key: string]: any;
+}
+
+export function useSearchParams(): AnyObject {
+ const { search } = useLocation();
+ return useMemo(() => {
+ const urlParams = new URLSearchParams(search);
+ const params: AnyObject = {};
+ for (const [key, value] of urlParams.entries()) {
+ params[key] = value;
+ }
+ return params;
+ }, [search]);
+}
diff --git a/frontend/src/hooks/useSliceUpload.tsx b/frontend/src/hooks/useSliceUpload.tsx
index 3301529bc..44a3dca6f 100644
--- a/frontend/src/hooks/useSliceUpload.tsx
+++ b/frontend/src/hooks/useSliceUpload.tsx
@@ -1,187 +1,187 @@
-import { TaskItem } from "@/pages/DataManagement/dataset.model";
-import { calculateSHA256, checkIsFilesExist } from "@/utils/file.util";
-import { App } from "antd";
-import { useRef, useState } from "react";
-
-export function useFileSliceUpload(
- {
- preUpload,
- uploadChunk,
- cancelUpload,
- }: {
- preUpload: (id: string, params: any) => Promise<{ data: number }>;
- uploadChunk: (id: string, formData: FormData, config: any) => Promise;
- cancelUpload: ((reqId: number) => Promise) | null;
- },
- showTaskCenter = true // 上传时是否显示任务中心
-) {
- const { message } = App.useApp();
- const [taskList, setTaskList] = useState([]);
- const taskListRef = useRef([]); // 用于固定任务顺序
-
- const createTask = (detail: any = {}) => {
- const { dataset } = detail;
- const title = `上传数据集: ${dataset.name} `;
- const controller = new AbortController();
- const task: TaskItem = {
- key: dataset.id,
- title,
- percent: 0,
- reqId: -1,
- controller,
- size: 0,
- updateEvent: detail.updateEvent,
- hasArchive: detail.hasArchive,
- };
- taskListRef.current = [task, ...taskListRef.current];
-
- setTaskList(taskListRef.current);
- return task;
- };
-
- const updateTaskList = (task: TaskItem) => {
- taskListRef.current = taskListRef.current.map((item) =>
- item.key === task.key ? task : item
- );
- setTaskList(taskListRef.current);
- };
-
- const removeTask = (task: TaskItem) => {
- const { key } = task;
- taskListRef.current = taskListRef.current.filter(
- (item) => item.key !== key
- );
- setTaskList(taskListRef.current);
- if (task.isCancel && task.cancelFn) {
- task.cancelFn();
- }
- if (task.updateEvent) window.dispatchEvent(new Event(task.updateEvent));
- if (showTaskCenter) {
- window.dispatchEvent(
- new CustomEvent("show:task-popover", { detail: { show: false } })
- );
- }
- };
-
- async function buildFormData({ file, reqId, i, j }) {
- const formData = new FormData();
- const { slices, name, size } = file;
- const checkSum = await calculateSHA256(slices[j]);
- formData.append("file", slices[j]);
- formData.append("reqId", reqId.toString());
- formData.append("fileNo", (i + 1).toString());
- formData.append("chunkNo", (j + 1).toString());
- formData.append("fileName", name);
- formData.append("fileSize", size.toString());
- formData.append("totalChunkNum", slices.length.toString());
- formData.append("checkSumHex", checkSum);
- return formData;
- }
-
- async function uploadSlice(task: TaskItem, fileInfo) {
- if (!task) {
- return;
- }
- const { reqId, key } = task;
- const { loaded, i, j, files, totalSize } = fileInfo;
- const formData = await buildFormData({
- file: files[i],
- i,
- j,
- reqId,
- });
-
- let newTask = { ...task };
- await uploadChunk(key, formData, {
- onUploadProgress: (e) => {
- const loadedSize = loaded + e.loaded;
- const curPercent = Number((loadedSize / totalSize) * 100).toFixed(2);
-
- newTask = {
- ...newTask,
- ...taskListRef.current.find((item) => item.key === key),
- size: loadedSize,
- percent: curPercent >= 100 ? 99.99 : curPercent,
- };
- updateTaskList(newTask);
- },
- });
- }
-
- async function uploadFile({ task, files, totalSize }) {
- const { data: reqId } = await preUpload(task.key, {
- totalFileNum: files.length,
- totalSize,
- datasetId: task.key,
- hasArchive: task.hasArchive,
- });
-
- const newTask: TaskItem = {
- ...task,
- reqId,
- isCancel: false,
- cancelFn: () => {
- task.controller.abort();
- cancelUpload?.(reqId);
- if (task.updateEvent) window.dispatchEvent(new Event(task.updateEvent));
- },
- };
- updateTaskList(newTask);
- if (showTaskCenter) {
- window.dispatchEvent(
- new CustomEvent("show:task-popover", { detail: { show: true } })
- );
- }
- // // 更新数据状态
- if (task.updateEvent) window.dispatchEvent(new Event(task.updateEvent));
-
- let loaded = 0;
- for (let i = 0; i < files.length; i++) {
- const { slices } = files[i];
- for (let j = 0; j < slices.length; j++) {
- await uploadSlice(newTask, {
- loaded,
- i,
- j,
- files,
- totalSize,
- });
- loaded += slices[j].size;
- }
- }
- removeTask(newTask);
- }
-
- const handleUpload = async ({ task, files }) => {
- const isErrorFile = await checkIsFilesExist(files);
- if (isErrorFile) {
- message.error("文件被修改或删除,请重新选择文件上传");
- removeTask({
- ...task,
- isCancel: false,
- ...taskListRef.current.find((item) => item.key === task.key),
- });
- return;
- }
-
- try {
- const totalSize = files.reduce((acc, file) => acc + file.size, 0);
- await uploadFile({ task, files, totalSize });
- } catch (err) {
- console.error(err);
- message.error("文件上传失败,请稍后重试");
- removeTask({
- ...task,
- isCancel: true,
- ...taskListRef.current.find((item) => item.key === task.key),
- });
- }
- };
-
- return {
- taskList,
- createTask,
- removeTask,
- handleUpload,
- };
-}
+import { TaskItem } from "@/pages/DataManagement/dataset.model";
+import { calculateSHA256, checkIsFilesExist } from "@/utils/file.util";
+import { App } from "antd";
+import { useRef, useState } from "react";
+
+export function useFileSliceUpload(
+ {
+ preUpload,
+ uploadChunk,
+ cancelUpload,
+ }: {
+ preUpload: (id: string, params: any) => Promise<{ data: number }>;
+ uploadChunk: (id: string, formData: FormData, config: any) => Promise;
+ cancelUpload: ((reqId: number) => Promise) | null;
+ },
+ showTaskCenter = true // 上传时是否显示任务中心
+) {
+ const { message } = App.useApp();
+ const [taskList, setTaskList] = useState([]);
+ const taskListRef = useRef([]); // 用于固定任务顺序
+
+ const createTask = (detail: any = {}) => {
+ const { dataset } = detail;
+ const title = `上传数据集: ${dataset.name} `;
+ const controller = new AbortController();
+ const task: TaskItem = {
+ key: dataset.id,
+ title,
+ percent: 0,
+ reqId: -1,
+ controller,
+ size: 0,
+ updateEvent: detail.updateEvent,
+ hasArchive: detail.hasArchive,
+ };
+ taskListRef.current = [task, ...taskListRef.current];
+
+ setTaskList(taskListRef.current);
+ return task;
+ };
+
+ const updateTaskList = (task: TaskItem) => {
+ taskListRef.current = taskListRef.current.map((item) =>
+ item.key === task.key ? task : item
+ );
+ setTaskList(taskListRef.current);
+ };
+
+ const removeTask = (task: TaskItem) => {
+ const { key } = task;
+ taskListRef.current = taskListRef.current.filter(
+ (item) => item.key !== key
+ );
+ setTaskList(taskListRef.current);
+ if (task.isCancel && task.cancelFn) {
+ task.cancelFn();
+ }
+ if (task.updateEvent) window.dispatchEvent(new Event(task.updateEvent));
+ if (showTaskCenter) {
+ window.dispatchEvent(
+ new CustomEvent("show:task-popover", { detail: { show: false } })
+ );
+ }
+ };
+
+ async function buildFormData({ file, reqId, i, j }) {
+ const formData = new FormData();
+ const { slices, name, size } = file;
+ const checkSum = await calculateSHA256(slices[j]);
+ formData.append("file", slices[j]);
+ formData.append("reqId", reqId.toString());
+ formData.append("fileNo", (i + 1).toString());
+ formData.append("chunkNo", (j + 1).toString());
+ formData.append("fileName", name);
+ formData.append("fileSize", size.toString());
+ formData.append("totalChunkNum", slices.length.toString());
+ formData.append("checkSumHex", checkSum);
+ return formData;
+ }
+
+ async function uploadSlice(task: TaskItem, fileInfo) {
+ if (!task) {
+ return;
+ }
+ const { reqId, key } = task;
+ const { loaded, i, j, files, totalSize } = fileInfo;
+ const formData = await buildFormData({
+ file: files[i],
+ i,
+ j,
+ reqId,
+ });
+
+ let newTask = { ...task };
+ await uploadChunk(key, formData, {
+ onUploadProgress: (e) => {
+ const loadedSize = loaded + e.loaded;
+ const curPercent = Number((loadedSize / totalSize) * 100).toFixed(2);
+
+ newTask = {
+ ...newTask,
+ ...taskListRef.current.find((item) => item.key === key),
+ size: loadedSize,
+ percent: curPercent >= 100 ? 99.99 : curPercent,
+ };
+ updateTaskList(newTask);
+ },
+ });
+ }
+
+ async function uploadFile({ task, files, totalSize }) {
+ const { data: reqId } = await preUpload(task.key, {
+ totalFileNum: files.length,
+ totalSize,
+ datasetId: task.key,
+ hasArchive: task.hasArchive,
+ });
+
+ const newTask: TaskItem = {
+ ...task,
+ reqId,
+ isCancel: false,
+ cancelFn: () => {
+ task.controller.abort();
+ cancelUpload?.(reqId);
+ if (task.updateEvent) window.dispatchEvent(new Event(task.updateEvent));
+ },
+ };
+ updateTaskList(newTask);
+ if (showTaskCenter) {
+ window.dispatchEvent(
+ new CustomEvent("show:task-popover", { detail: { show: true } })
+ );
+ }
+ // // 更新数据状态
+ if (task.updateEvent) window.dispatchEvent(new Event(task.updateEvent));
+
+ let loaded = 0;
+ for (let i = 0; i < files.length; i++) {
+ const { slices } = files[i];
+ for (let j = 0; j < slices.length; j++) {
+ await uploadSlice(newTask, {
+ loaded,
+ i,
+ j,
+ files,
+ totalSize,
+ });
+ loaded += slices[j].size;
+ }
+ }
+ removeTask(newTask);
+ }
+
+ const handleUpload = async ({ task, files }) => {
+ const isErrorFile = await checkIsFilesExist(files);
+ if (isErrorFile) {
+ message.error("文件被修改或删除,请重新选择文件上传");
+ removeTask({
+ ...task,
+ isCancel: false,
+ ...taskListRef.current.find((item) => item.key === task.key),
+ });
+ return;
+ }
+
+ try {
+ const totalSize = files.reduce((acc, file) => acc + file.size, 0);
+ await uploadFile({ task, files, totalSize });
+ } catch (err) {
+ console.error(err);
+ message.error("文件上传失败,请稍后重试");
+ removeTask({
+ ...task,
+ isCancel: true,
+ ...taskListRef.current.find((item) => item.key === task.key),
+ });
+ }
+ };
+
+ return {
+ taskList,
+ createTask,
+ removeTask,
+ handleUpload,
+ };
+}
diff --git a/frontend/src/hooks/useStyle.ts b/frontend/src/hooks/useStyle.ts
index ba694baa1..5139240e7 100644
--- a/frontend/src/hooks/useStyle.ts
+++ b/frontend/src/hooks/useStyle.ts
@@ -1,20 +1,20 @@
-import { createStyles } from "antd-style";
-
-const useStyle = createStyles(({ css, token }) => {
- const { antCls } = token;
- return {
- customTable: css`
- ${antCls}-table {
- ${antCls}-table-container {
- ${antCls}-table-body, ${antCls}-table-content {
- scrollbar-width: thin;
- scrollbar-color: ${token.colorBorder} transparent;
- scrollbar-gutter: stable;
- }
- }
- }
- `,
- };
-});
-
-export default useStyle;
+import { createStyles } from "antd-style";
+
+const useStyle = createStyles(({ css, token }) => {
+ const { antCls } = token;
+ return {
+ customTable: css`
+ ${antCls}-table {
+ ${antCls}-table-container {
+ ${antCls}-table-body, ${antCls}-table-content {
+ scrollbar-width: thin;
+ scrollbar-color: ${token.colorBorder} transparent;
+ scrollbar-gutter: stable;
+ }
+ }
+ }
+ `,
+ };
+});
+
+export default useStyle;
diff --git a/frontend/src/hooks/useTagConfig.ts b/frontend/src/hooks/useTagConfig.ts
index ec39524f7..d547c54ac 100644
--- a/frontend/src/hooks/useTagConfig.ts
+++ b/frontend/src/hooks/useTagConfig.ts
@@ -1,67 +1,67 @@
-import { useState, useEffect } from "react";
-import { message } from "antd";
-import { getTagConfigUsingGet } from "../pages/DataAnnotation/annotation.api";
-import type { LabelStudioTagConfig } from "../pages/DataAnnotation/annotation.tagconfig";
-import { parseTagConfig, type TagOption } from "../pages/DataAnnotation/annotation.tagconfig";
-
-interface UseTagConfigReturn {
- config: LabelStudioTagConfig | null;
- objectOptions: TagOption[];
- controlOptions: TagOption[];
- loading: boolean;
- error: string | null;
- refetch: () => Promise;
-}
-
-/**
- * Hook to fetch and manage Label Studio tag configuration
- * @param includeLabelingOnly - If true, only include controls with category="labeling" (default: true)
- */
-export function useTagConfig(includeLabelingOnly: boolean = true): UseTagConfigReturn {
- const [config, setConfig] = useState(null);
- const [objectOptions, setObjectOptions] = useState([]);
- const [controlOptions, setControlOptions] = useState([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
-
- const fetchConfig = async () => {
- setLoading(true);
- setError(null);
- try {
- const response = await getTagConfigUsingGet();
- if (response.code === 200 && response.data) {
- const tagConfig: LabelStudioTagConfig = response.data;
- setConfig(tagConfig);
-
- const { objectOptions: objects, controlOptions: controls } =
- parseTagConfig(tagConfig, includeLabelingOnly);
- setObjectOptions(objects);
- setControlOptions(controls);
- } else {
- const errorMsg = response.message || "获取标签配置失败";
- setError(errorMsg);
- message.error(errorMsg);
- }
- } catch (err: any) {
- const errorMsg = err.message || "加载标签配置时出错";
- setError(errorMsg);
- console.error("Failed to fetch tag config:", err);
- message.error(errorMsg);
- } finally {
- setLoading(false);
- }
- };
-
- useEffect(() => {
- fetchConfig();
- }, []);
-
- return {
- config,
- objectOptions,
- controlOptions,
- loading,
- error,
- refetch: fetchConfig,
- };
-}
+import { useState, useEffect } from "react";
+import { message } from "antd";
+import { getTagConfigUsingGet } from "../pages/DataAnnotation/annotation.api";
+import type { LabelStudioTagConfig } from "../pages/DataAnnotation/annotation.tagconfig";
+import { parseTagConfig, type TagOption } from "../pages/DataAnnotation/annotation.tagconfig";
+
+interface UseTagConfigReturn {
+ config: LabelStudioTagConfig | null;
+ objectOptions: TagOption[];
+ controlOptions: TagOption[];
+ loading: boolean;
+ error: string | null;
+ refetch: () => Promise;
+}
+
+/**
+ * Hook to fetch and manage Label Studio tag configuration
+ * @param includeLabelingOnly - If true, only include controls with category="labeling" (default: true)
+ */
+export function useTagConfig(includeLabelingOnly: boolean = true): UseTagConfigReturn {
+ const [config, setConfig] = useState(null);
+ const [objectOptions, setObjectOptions] = useState([]);
+ const [controlOptions, setControlOptions] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const fetchConfig = async () => {
+ setLoading(true);
+ setError(null);
+ try {
+ const response = await getTagConfigUsingGet();
+ if (response.code === 200 && response.data) {
+ const tagConfig: LabelStudioTagConfig = response.data;
+ setConfig(tagConfig);
+
+ const { objectOptions: objects, controlOptions: controls } =
+ parseTagConfig(tagConfig, includeLabelingOnly);
+ setObjectOptions(objects);
+ setControlOptions(controls);
+ } else {
+ const errorMsg = response.message || "获取标签配置失败";
+ setError(errorMsg);
+ message.error(errorMsg);
+ }
+ } catch (err: any) {
+ const errorMsg = err.message || "加载标签配置时出错";
+ setError(errorMsg);
+ console.error("Failed to fetch tag config:", err);
+ message.error(errorMsg);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchConfig();
+ }, []);
+
+ return {
+ config,
+ objectOptions,
+ controlOptions,
+ loading,
+ error,
+ refetch: fetchConfig,
+ };
+}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index d08ea97e1..172fcbb1a 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,75 +1,75 @@
-@import "tailwindcss";
-
-/* components/TopLoadingBar.css */
-.top-loading-bar {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 3px;
- background-color: transparent;
- z-index: 9999;
- overflow: hidden;
-}
-
-.loading-bar-progress {
- height: 100%;
- background: linear-gradient(90deg, #3498db, #2ecc71, #3498db);
- background-size: 200% 100%;
- animation: gradient-animation 2s linear infinite, width-animation 0.3s ease;
- transition: width 0.3s ease;
-}
-
-@keyframes gradient-animation {
- 0% {
- background-position: 200% 0;
- }
- 100% {
- background-position: -200% 0;
- }
-}
-
-@keyframes width-animation {
- from {
- transform: translateX(-100%);
- }
- to {
- transform: translateX(0);
- }
-}
-
-.show-task-popover {
- opacity: 100%;
- visibility: visible;
- transform: translateX(0);
-}
-
-@layer components {
- .flex-center {
- @apply flex items-center justify-center;
- }
- .flex-overflow-auto {
- @apply flex-1 flex flex-col overflow-auto h-full;
- }
- .flex-overflow-hidden {
- @apply flex flex-col h-full overflow-hidden;
- }
- .border-card {
- @apply border border-[#f0f0f0] rounded-lg bg-white;
- }
- .border {
- @apply border border-[#f0f0f0];
- }
- .border-bottom {
- @apply border-b border-[#f0f0f0];
- }
- .border-top {
- @apply border-t border-[#f0f0f0];
- }
- .border-right {
- @apply border-r border-[#f0f0f0];
- }
- .border-left {
- @apply border-l border-[#f0f0f0];
- }
-}
+@import "tailwindcss";
+
+/* components/TopLoadingBar.css */
+.top-loading-bar {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 3px;
+ background-color: transparent;
+ z-index: 9999;
+ overflow: hidden;
+}
+
+.loading-bar-progress {
+ height: 100%;
+ background: linear-gradient(90deg, #3498db, #2ecc71, #3498db);
+ background-size: 200% 100%;
+ animation: gradient-animation 2s linear infinite, width-animation 0.3s ease;
+ transition: width 0.3s ease;
+}
+
+@keyframes gradient-animation {
+ 0% {
+ background-position: 200% 0;
+ }
+ 100% {
+ background-position: -200% 0;
+ }
+}
+
+@keyframes width-animation {
+ from {
+ transform: translateX(-100%);
+ }
+ to {
+ transform: translateX(0);
+ }
+}
+
+.show-task-popover {
+ opacity: 100%;
+ visibility: visible;
+ transform: translateX(0);
+}
+
+@layer components {
+ .flex-center {
+ @apply flex items-center justify-center;
+ }
+ .flex-overflow-auto {
+ @apply flex-1 flex flex-col overflow-auto h-full;
+ }
+ .flex-overflow-hidden {
+ @apply flex flex-col h-full overflow-hidden;
+ }
+ .border-card {
+ @apply border border-[#f0f0f0] rounded-lg bg-white;
+ }
+ .border {
+ @apply border border-[#f0f0f0];
+ }
+ .border-bottom {
+ @apply border-b border-[#f0f0f0];
+ }
+ .border-top {
+ @apply border-t border-[#f0f0f0];
+ }
+ .border-right {
+ @apply border-r border-[#f0f0f0];
+ }
+ .border-left {
+ @apply border-l border-[#f0f0f0];
+ }
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index a3f0f01bb..4537c90ef 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,22 +1,22 @@
-import { StrictMode, Suspense } from "react";
-import { createRoot } from "react-dom/client";
-import { RouterProvider } from "react-router";
-import router from "./routes/routes";
-import { App as AntdApp, Spin } from "antd";
-import "./index.css";
-import TopLoadingBar from "./components/TopLoadingBar";
-import { store } from "./store";
-import { Provider } from "react-redux";
-
-createRoot(document.getElementById("root")!).render(
-
-
-
- }>
-
-
-
-
-
-
-);
+import { StrictMode, Suspense } from "react";
+import { createRoot } from "react-dom/client";
+import { RouterProvider } from "react-router";
+import router from "./routes/routes";
+import { App as AntdApp, Spin } from "antd";
+import "./index.css";
+import TopLoadingBar from "./components/TopLoadingBar";
+import { store } from "./store";
+import { Provider } from "react-redux";
+
+createRoot(document.getElementById("root")!).render(
+
+
+
+ }>
+
+
+
+
+
+
+);
diff --git a/frontend/src/mock/annotation.tsx b/frontend/src/mock/annotation.tsx
index 8b5ccccf2..280815dbf 100644
--- a/frontend/src/mock/annotation.tsx
+++ b/frontend/src/mock/annotation.tsx
@@ -1,330 +1,330 @@
-import { BarChart, Circle, Grid, ImageIcon, Layers, Maximize, MousePointer, Move, Square, Target, Crop, RotateCcw, FileText, Tag, Heart, HelpCircle, BookOpen, MessageSquare, Users, Zap, Globe, Scissors } from "lucide-react";
-
-// Define the AnnotationTask type if not imported from elsewhere
-interface AnnotationTask {
- id: string
- name: string
- completed: string
- completedCount: number
- skippedCount: number
- totalCount: number
- annotators: Array<{
- id: string
- name: string
- avatar?: string
- }>
- text: string
- status: "completed" | "in_progress" | "pending" | "skipped"
- project: string
- type: "图像分类" | "文本分类" | "目标检测" | "NER" | "语音识别" | "视频分析"
- datasetType: "text" | "image" | "video" | "audio"
- progress: number
-}
-
-
-export const mockTasks: AnnotationTask[] = [
- {
- id: "12345678",
- name: "图像分类标注任务",
- completed: "2024年1月20日 20:40",
- completedCount: 1,
- skippedCount: 0,
- totalCount: 2,
- annotators: [
- { id: "1", name: "张三", avatar: "/placeholder-user.jpg" },
- { id: "2", name: "李四", avatar: "/placeholder-user.jpg" },
- ],
- text: "对产品图像进行分类标注,包含10个类别",
- status: "completed",
- project: "图像分类",
- type: "图像分类",
- datasetType: "image",
- progress: 100,
- },
- {
- id: "12345679",
- name: "文本情感分析标注",
- completed: "2024年1月20日 20:40",
- completedCount: 2,
- skippedCount: 0,
- totalCount: 2,
- annotators: [
- { id: "1", name: "王五", avatar: "/placeholder-user.jpg" },
- { id: "2", name: "赵六", avatar: "/placeholder-user.jpg" },
- ],
- text: "对用户评论进行情感倾向标注",
- status: "completed",
- project: "文本分类",
- type: "文本分类",
- datasetType: "text",
- progress: 100,
- },
- {
- id: "12345680",
- name: "目标检测标注任务",
- completed: "2024年1月20日 20:40",
- completedCount: 1,
- skippedCount: 0,
- totalCount: 2,
- annotators: [{ id: "1", name: "孙七", avatar: "/placeholder-user.jpg" }],
- text: "对交通场景图像进行目标检测标注",
- status: "in_progress",
- project: "目标检测",
- type: "目标检测",
- datasetType: "image",
- progress: 50,
- },
- {
- id: "12345681",
- name: "命名实体识别标注",
- completed: "2024年1月20日 20:40",
- completedCount: 1,
- skippedCount: 0,
- totalCount: 2,
- annotators: [{ id: "1", name: "周八", avatar: "/placeholder-user.jpg" }],
- text: "对新闻文本进行命名实体识别标注",
- status: "in_progress",
- project: "NER",
- type: "NER",
- datasetType: "text",
- progress: 75,
- },
- {
- id: "12345682",
- name: "语音识别标注任务",
- completed: "2024年1月20日 20:40",
- completedCount: 1,
- skippedCount: 0,
- totalCount: 2,
- annotators: [{ id: "1", name: "吴九", avatar: "/placeholder-user.jpg" }],
- text: "对语音数据进行转录和标注",
- status: "in_progress",
- project: "语音识别",
- type: "语音识别",
- datasetType: "audio",
- progress: 25,
- },
- {
- id: "12345683",
- name: "视频动作识别标注",
- completed: "2024年1月20日 20:40",
- completedCount: 0,
- skippedCount: 2,
- totalCount: 2,
- annotators: [
- { id: "1", name: "陈十", avatar: "/placeholder-user.jpg" },
- { id: "2", name: "林十一", avatar: "/placeholder-user.jpg" },
- ],
- text: "对视频中的人体动作进行识别和标注",
- status: "skipped",
- project: "视频分析",
- type: "视频分析",
- datasetType: "video",
- progress: 0,
- },
-]
-
-// Define the Template type
-type Template = {
- id: string;
- name: string;
- category: string;
- description: string;
- type: string;
- preview?: string;
- icon: React.ReactNode;
-};
-
-// 扩展的预制模板数据
-export const mockTemplates: Template[] = [
- // 计算机视觉模板
- {
- id: "cv-1",
- name: "目标检测",
- category: "Computer Vision",
- description: "使用边界框标注图像中的目标对象",
- type: "image",
- preview: "/placeholder.svg?height=120&width=180&text=Object+Detection",
- icon: ,
- },
- {
- id: "cv-2",
- name: "语义分割(多边形)",
- category: "Computer Vision",
- description: "使用多边形精确标注图像中的区域",
- type: "image",
- preview: "/placeholder.svg?height=120&width=180&text=Polygon+Segmentation",
- icon: ,
- },
- {
- id: "cv-3",
- name: "语义分割(掩码)",
- category: "Computer Vision",
- description: "使用像素级掩码标注图像区域",
- type: "image",
- preview: "/placeholder.svg?height=120&width=180&text=Mask+Segmentation",
- icon: ,
- },
- {
- id: "cv-4",
- name: "关键点标注",
- category: "Computer Vision",
- description: "标注图像中的关键点位置",
- type: "image",
- preview: "/placeholder.svg?height=120&width=180&text=Keypoint+Labeling",
- icon: ,
- },
- {
- id: "cv-5",
- name: "图像分类",
- category: "Computer Vision",
- description: "为整个图像分配类别标签",
- type: "image",
- preview: "/placeholder.svg?height=120&width=180&text=Image+Classification",
- icon: ,
- },
- {
- id: "cv-6",
- name: "实例分割",
- category: "Computer Vision",
- description: "区分同类别的不同实例对象",
- type: "image",
- preview: "/placeholder.svg?height=120&width=180&text=Instance+Segmentation",
- icon: ,
- },
- {
- id: "cv-7",
- name: "全景分割",
- category: "Computer Vision",
- description: "结合语义分割和实例分割的全景标注",
- type: "image",
- preview: "/placeholder.svg?height=120&width=180&text=Panoptic+Segmentation",
- icon: ,
- },
- {
- id: "cv-8",
- name: "3D目标检测",
- category: "Computer Vision",
- description: "在3D空间中标注目标对象的位置和方向",
- type: "image",
- preview: "/placeholder.svg?height=120&width=180&text=3D+Object+Detection",
- icon: ,
- },
- {
- id: "cv-9",
- name: "图像配对",
- category: "Computer Vision",
- description: "标注图像之间的对应关系",
- type: "image",
- preview: "/placeholder.svg?height=120&width=180&text=Image+Matching",
- icon: ,
- },
- {
- id: "cv-10",
- name: "图像质量评估",
- category: "Computer Vision",
- description: "评估和标注图像质量等级",
- type: "image",
- preview: "/placeholder.svg?height=120&width=180&text=Quality+Assessment",
- icon: ,
- },
- {
- id: "cv-11",
- name: "图像裁剪标注",
- category: "Computer Vision",
- description: "标注图像中需要裁剪的区域",
- type: "image",
- preview: "/placeholder.svg?height=120&width=180&text=Image+Cropping",
- icon: ,
- },
- {
- id: "cv-12",
- name: "图像旋转标注",
- category: "Computer Vision",
- description: "标注图像的正确方向角度",
- type: "image",
- preview: "/placeholder.svg?height=120&width=180&text=Image+Rotation",
- icon: ,
- },
- // 自然语言处理模板
- {
- id: "nlp-1",
- name: "文本分类",
- category: "Natural Language Processing",
- description: "为文本分配类别标签",
- type: "text",
- icon: ,
- },
- {
- id: "nlp-2",
- name: "命名实体识别",
- category: "Natural Language Processing",
- description: "识别和标注文本中的实体",
- type: "text",
- icon: ,
- },
- {
- id: "nlp-3",
- name: "情感分析",
- category: "Natural Language Processing",
- description: "标注文本的情感倾向",
- type: "text",
- icon: ,
- },
- {
- id: "nlp-4",
- name: "问答标注",
- category: "Natural Language Processing",
- description: "标注问题和答案对",
- type: "text",
- icon: ,
- },
- {
- id: "nlp-5",
- name: "文本摘要",
- category: "Natural Language Processing",
- description: "为长文本创建摘要标注",
- type: "text",
- icon: ,
- },
- {
- id: "nlp-6",
- name: "对话标注",
- category: "Natural Language Processing",
- description: "标注对话中的意图和实体",
- type: "text",
- icon: ,
- },
- {
- id: "nlp-7",
- name: "关系抽取",
- category: "Natural Language Processing",
- description: "标注实体之间的关系",
- type: "text",
- icon: ,
- },
- {
- id: "nlp-8",
- name: "文本相似度",
- category: "Natural Language Processing",
- description: "标注文本之间的相似度",
- type: "text",
- icon: ,
- },
- {
- id: "nlp-9",
- name: "语言检测",
- category: "Natural Language Processing",
- description: "识别和标注文本的语言类型",
- type: "text",
- icon: ,
- },
- {
- id: "nlp-10",
- name: "文本纠错",
- category: "Natural Language Processing",
- description: "标注文本中的错误并提供修正",
- type: "text",
- icon: ,
- },
+import { BarChart, Circle, Grid, ImageIcon, Layers, Maximize, MousePointer, Move, Square, Target, Crop, RotateCcw, FileText, Tag, Heart, HelpCircle, BookOpen, MessageSquare, Users, Zap, Globe, Scissors } from "lucide-react";
+
+// Define the AnnotationTask type if not imported from elsewhere
+interface AnnotationTask {
+ id: string
+ name: string
+ completed: string
+ completedCount: number
+ skippedCount: number
+ totalCount: number
+ annotators: Array<{
+ id: string
+ name: string
+ avatar?: string
+ }>
+ text: string
+ status: "completed" | "in_progress" | "pending" | "skipped"
+ project: string
+ type: "图像分类" | "文本分类" | "目标检测" | "NER" | "语音识别" | "视频分析"
+ datasetType: "text" | "image" | "video" | "audio"
+ progress: number
+}
+
+
+export const mockTasks: AnnotationTask[] = [
+ {
+ id: "12345678",
+ name: "图像分类标注任务",
+ completed: "2024年1月20日 20:40",
+ completedCount: 1,
+ skippedCount: 0,
+ totalCount: 2,
+ annotators: [
+ { id: "1", name: "张三", avatar: "/placeholder-user.jpg" },
+ { id: "2", name: "李四", avatar: "/placeholder-user.jpg" },
+ ],
+ text: "对产品图像进行分类标注,包含10个类别",
+ status: "completed",
+ project: "图像分类",
+ type: "图像分类",
+ datasetType: "image",
+ progress: 100,
+ },
+ {
+ id: "12345679",
+ name: "文本情感分析标注",
+ completed: "2024年1月20日 20:40",
+ completedCount: 2,
+ skippedCount: 0,
+ totalCount: 2,
+ annotators: [
+ { id: "1", name: "王五", avatar: "/placeholder-user.jpg" },
+ { id: "2", name: "赵六", avatar: "/placeholder-user.jpg" },
+ ],
+ text: "对用户评论进行情感倾向标注",
+ status: "completed",
+ project: "文本分类",
+ type: "文本分类",
+ datasetType: "text",
+ progress: 100,
+ },
+ {
+ id: "12345680",
+ name: "目标检测标注任务",
+ completed: "2024年1月20日 20:40",
+ completedCount: 1,
+ skippedCount: 0,
+ totalCount: 2,
+ annotators: [{ id: "1", name: "孙七", avatar: "/placeholder-user.jpg" }],
+ text: "对交通场景图像进行目标检测标注",
+ status: "in_progress",
+ project: "目标检测",
+ type: "目标检测",
+ datasetType: "image",
+ progress: 50,
+ },
+ {
+ id: "12345681",
+ name: "命名实体识别标注",
+ completed: "2024年1月20日 20:40",
+ completedCount: 1,
+ skippedCount: 0,
+ totalCount: 2,
+ annotators: [{ id: "1", name: "周八", avatar: "/placeholder-user.jpg" }],
+ text: "对新闻文本进行命名实体识别标注",
+ status: "in_progress",
+ project: "NER",
+ type: "NER",
+ datasetType: "text",
+ progress: 75,
+ },
+ {
+ id: "12345682",
+ name: "语音识别标注任务",
+ completed: "2024年1月20日 20:40",
+ completedCount: 1,
+ skippedCount: 0,
+ totalCount: 2,
+ annotators: [{ id: "1", name: "吴九", avatar: "/placeholder-user.jpg" }],
+ text: "对语音数据进行转录和标注",
+ status: "in_progress",
+ project: "语音识别",
+ type: "语音识别",
+ datasetType: "audio",
+ progress: 25,
+ },
+ {
+ id: "12345683",
+ name: "视频动作识别标注",
+ completed: "2024年1月20日 20:40",
+ completedCount: 0,
+ skippedCount: 2,
+ totalCount: 2,
+ annotators: [
+ { id: "1", name: "陈十", avatar: "/placeholder-user.jpg" },
+ { id: "2", name: "林十一", avatar: "/placeholder-user.jpg" },
+ ],
+ text: "对视频中的人体动作进行识别和标注",
+ status: "skipped",
+ project: "视频分析",
+ type: "视频分析",
+ datasetType: "video",
+ progress: 0,
+ },
+]
+
+// Define the Template type
+type Template = {
+ id: string;
+ name: string;
+ category: string;
+ description: string;
+ type: string;
+ preview?: string;
+ icon: React.ReactNode;
+};
+
+// 扩展的预制模板数据
+export const mockTemplates: Template[] = [
+ // 计算机视觉模板
+ {
+ id: "cv-1",
+ name: "目标检测",
+ category: "Computer Vision",
+ description: "使用边界框标注图像中的目标对象",
+ type: "image",
+ preview: "/placeholder.svg?height=120&width=180&text=Object+Detection",
+ icon: ,
+ },
+ {
+ id: "cv-2",
+ name: "语义分割(多边形)",
+ category: "Computer Vision",
+ description: "使用多边形精确标注图像中的区域",
+ type: "image",
+ preview: "/placeholder.svg?height=120&width=180&text=Polygon+Segmentation",
+ icon: ,
+ },
+ {
+ id: "cv-3",
+ name: "语义分割(掩码)",
+ category: "Computer Vision",
+ description: "使用像素级掩码标注图像区域",
+ type: "image",
+ preview: "/placeholder.svg?height=120&width=180&text=Mask+Segmentation",
+ icon: ,
+ },
+ {
+ id: "cv-4",
+ name: "关键点标注",
+ category: "Computer Vision",
+ description: "标注图像中的关键点位置",
+ type: "image",
+ preview: "/placeholder.svg?height=120&width=180&text=Keypoint+Labeling",
+ icon: ,
+ },
+ {
+ id: "cv-5",
+ name: "图像分类",
+ category: "Computer Vision",
+ description: "为整个图像分配类别标签",
+ type: "image",
+ preview: "/placeholder.svg?height=120&width=180&text=Image+Classification",
+ icon: ,
+ },
+ {
+ id: "cv-6",
+ name: "实例分割",
+ category: "Computer Vision",
+ description: "区分同类别的不同实例对象",
+ type: "image",
+ preview: "/placeholder.svg?height=120&width=180&text=Instance+Segmentation",
+ icon: ,
+ },
+ {
+ id: "cv-7",
+ name: "全景分割",
+ category: "Computer Vision",
+ description: "结合语义分割和实例分割的全景标注",
+ type: "image",
+ preview: "/placeholder.svg?height=120&width=180&text=Panoptic+Segmentation",
+ icon: ,
+ },
+ {
+ id: "cv-8",
+ name: "3D目标检测",
+ category: "Computer Vision",
+ description: "在3D空间中标注目标对象的位置和方向",
+ type: "image",
+ preview: "/placeholder.svg?height=120&width=180&text=3D+Object+Detection",
+ icon: ,
+ },
+ {
+ id: "cv-9",
+ name: "图像配对",
+ category: "Computer Vision",
+ description: "标注图像之间的对应关系",
+ type: "image",
+ preview: "/placeholder.svg?height=120&width=180&text=Image+Matching",
+ icon: ,
+ },
+ {
+ id: "cv-10",
+ name: "图像质量评估",
+ category: "Computer Vision",
+ description: "评估和标注图像质量等级",
+ type: "image",
+ preview: "/placeholder.svg?height=120&width=180&text=Quality+Assessment",
+ icon: ,
+ },
+ {
+ id: "cv-11",
+ name: "图像裁剪标注",
+ category: "Computer Vision",
+ description: "标注图像中需要裁剪的区域",
+ type: "image",
+ preview: "/placeholder.svg?height=120&width=180&text=Image+Cropping",
+ icon: ,
+ },
+ {
+ id: "cv-12",
+ name: "图像旋转标注",
+ category: "Computer Vision",
+ description: "标注图像的正确方向角度",
+ type: "image",
+ preview: "/placeholder.svg?height=120&width=180&text=Image+Rotation",
+ icon: ,
+ },
+ // 自然语言处理模板
+ {
+ id: "nlp-1",
+ name: "文本分类",
+ category: "Natural Language Processing",
+ description: "为文本分配类别标签",
+ type: "text",
+ icon: ,
+ },
+ {
+ id: "nlp-2",
+ name: "命名实体识别",
+ category: "Natural Language Processing",
+ description: "识别和标注文本中的实体",
+ type: "text",
+ icon: ,
+ },
+ {
+ id: "nlp-3",
+ name: "情感分析",
+ category: "Natural Language Processing",
+ description: "标注文本的情感倾向",
+ type: "text",
+ icon: ,
+ },
+ {
+ id: "nlp-4",
+ name: "问答标注",
+ category: "Natural Language Processing",
+ description: "标注问题和答案对",
+ type: "text",
+ icon: ,
+ },
+ {
+ id: "nlp-5",
+ name: "文本摘要",
+ category: "Natural Language Processing",
+ description: "为长文本创建摘要标注",
+ type: "text",
+ icon: ,
+ },
+ {
+ id: "nlp-6",
+ name: "对话标注",
+ category: "Natural Language Processing",
+ description: "标注对话中的意图和实体",
+ type: "text",
+ icon: ,
+ },
+ {
+ id: "nlp-7",
+ name: "关系抽取",
+ category: "Natural Language Processing",
+ description: "标注实体之间的关系",
+ type: "text",
+ icon: ,
+ },
+ {
+ id: "nlp-8",
+ name: "文本相似度",
+ category: "Natural Language Processing",
+ description: "标注文本之间的相似度",
+ type: "text",
+ icon: ,
+ },
+ {
+ id: "nlp-9",
+ name: "语言检测",
+ category: "Natural Language Processing",
+ description: "识别和标注文本的语言类型",
+ type: "text",
+ icon: ,
+ },
+ {
+ id: "nlp-10",
+ name: "文本纠错",
+ category: "Natural Language Processing",
+ description: "标注文本中的错误并提供修正",
+ type: "text",
+ icon: ,
+ },
]
\ No newline at end of file
diff --git a/frontend/src/mock/evaluation.tsx b/frontend/src/mock/evaluation.tsx
index 41bb0ba9c..5caad2da5 100644
--- a/frontend/src/mock/evaluation.tsx
+++ b/frontend/src/mock/evaluation.tsx
@@ -1,290 +1,290 @@
-// 预设评估维度配置
-export const presetEvaluationDimensions: EvaluationDimension[] = [
- {
- id: "answer_relevance",
- name: "回答相关性",
- description: "评估回答内容是否针对问题,是否切中要点",
- category: "accuracy",
- isEnabled: true,
- },
- {
- id: "content_quality",
- name: "内容质量",
- description: "评估内容的准确性、完整性和可读性",
- category: "quality",
- isEnabled: true,
- },
- {
- id: "information_completeness",
- name: "信息完整性",
- description: "评估信息是否完整,无缺失关键内容",
- category: "completeness",
- isEnabled: true,
- },
- {
- id: "language_fluency",
- name: "语言流畅性",
- description: "评估语言表达是否流畅自然",
- category: "quality",
- isEnabled: true,
- },
- {
- id: "factual_accuracy",
- name: "事实准确性",
- description: "评估内容中事实信息的准确性",
- category: "accuracy",
- isEnabled: true,
- },
-]
-
-
-export const sliceOperators: SliceOperator[] = [
- {
- id: "paragraph-split",
- name: "段落分割",
- description: "按段落自然分割文档",
- type: "text",
- icon: "📄",
- params: { minLength: 50, maxLength: 1000 },
- },
- {
- id: "sentence-split",
- name: "句子分割",
- description: "按句子边界分割文档",
- type: "text",
- icon: "📝",
- params: { maxSentences: 5, overlap: 1 },
- },
- {
- id: "semantic-split",
- name: "语义分割",
- description: "基于语义相似度智能分割",
- type: "semantic",
- icon: "🧠",
- params: { threshold: 0.7, windowSize: 3 },
- },
- {
- id: "length-split",
- name: "长度分割",
- description: "按固定字符长度分割",
- type: "text",
- icon: "📏",
- params: { chunkSize: 512, overlap: 50 },
- },
- {
- id: "structure-split",
- name: "结构化分割",
- description: "按文档结构(标题、章节)分割",
- type: "structure",
- icon: "🏗️",
- params: { preserveHeaders: true, minSectionLength: 100 },
- },
- {
- id: "table-extract",
- name: "表格提取",
- description: "提取并单独处理表格内容",
- type: "structure",
- icon: "📊",
- params: { includeHeaders: true, mergeRows: false },
- },
- {
- id: "code-extract",
- name: "代码提取",
- description: "识别并提取代码块",
- type: "custom",
- icon: "💻",
- params: { languages: ["python", "javascript", "sql"], preserveIndentation: true },
- },
- {
- id: "qa-extract",
- name: "问答提取",
- description: "自动识别问答格式内容",
- type: "semantic",
- icon: "❓",
- params: { confidenceThreshold: 0.8, generateAnswers: true },
- },
-]
-
-
-export const mockTasks: EvaluationTask[] = [
- {
- id: "1",
- name: "客服对话数据质量评估",
- datasetId: "1",
- datasetName: "客服对话数据集",
- evaluationType: "model",
- status: "completed",
- score: 85,
- progress: 100,
- createdAt: "2024-01-15 14:30",
- completedAt: "2024-01-15 14:45",
- description: "评估客服对话数据的质量,包括对话完整性、回复准确性等维度",
- dimensions: ["answer_relevance", "content_quality", "information_completeness"],
- customDimensions: [],
- sliceConfig: {
- threshold: 0.8,
- sampleCount: 100,
- method: "语义分割",
- },
- modelConfig: {
- url: "https://api.openai.com/v1/chat/completions",
- apiKey: "sk-***",
- prompt: "请从数据质量、标签准确性、标注一致性三个维度评估这个客服对话数据集...",
- temperature: 0.3,
- maxTokens: 2000,
- },
- metrics: {
- accuracy: 88,
- completeness: 92,
- consistency: 78,
- relevance: 85,
- },
- issues: [
- { type: "重复数据", count: 23, severity: "medium" },
- { type: "格式错误", count: 5, severity: "high" },
- { type: "内容不完整", count: 12, severity: "low" },
- ],
- },
- {
- id: "2",
- name: "产品评论人工评估",
- datasetId: "2",
- datasetName: "产品评论数据集",
- evaluationType: "manual",
- status: "pending",
- progress: 0,
- createdAt: "2024-01-15 15:20",
- description: "人工评估产品评论数据的情感标注准确性",
- dimensions: ["content_quality", "factual_accuracy"],
- customDimensions: [
- {
- id: "custom_1",
- name: "情感极性准确性",
- description: "评估情感标注的极性(正面/负面/中性)准确性",
- category: "custom",
- isCustom: true,
- isEnabled: true,
- },
- ],
- sliceConfig: {
- threshold: 0.7,
- sampleCount: 50,
- method: "段落分割",
- },
- metrics: {
- accuracy: 0,
- completeness: 0,
- consistency: 0,
- relevance: 0,
- },
- issues: [],
- },
- {
- id: "3",
- name: "新闻分类数据评估",
- datasetId: "4",
- datasetName: "新闻分类数据集",
- evaluationType: "manual",
- status: "running",
- progress: 65,
- createdAt: "2024-01-15 16:10",
- description: "人工评估新闻分类数据集的标注质量",
- dimensions: ["content_quality", "information_completeness", "factual_accuracy"],
- customDimensions: [],
- sliceConfig: {
- threshold: 0.9,
- sampleCount: 80,
- method: "句子分割",
- },
- metrics: {
- accuracy: 82,
- completeness: 78,
- consistency: 85,
- relevance: 80,
- },
- issues: [{ type: "标注不一致", count: 15, severity: "medium" }],
- },
-]
-
-// 模拟QA对数据
-export const mockQAPairs: QAPair[] = [
- {
- id: "qa_1",
- question: "这个产品的退货政策是什么?",
- answer: "我们提供7天无理由退货服务,商品需要保持原包装完整。",
- sliceId: "slice_1",
- score: 4.5,
- feedback: "回答准确且完整",
- },
- {
- id: "qa_2",
- question: "如何联系客服?",
- answer: "您可以通过在线客服、电话400-123-4567或邮箱service@company.com联系我们。",
- sliceId: "slice_2",
- score: 5.0,
- feedback: "提供了多种联系方式,非常全面",
- },
- {
- id: "qa_3",
- question: "配送时间需要多久?",
- answer: "一般情况下,我们会在1-3个工作日内发货,配送时间根据地区不同为2-7天。",
- sliceId: "slice_3",
- score: 4.0,
- feedback: "时间范围说明清楚",
- },
-]
-// 评估维度模板配置
-export const evaluationTemplates = {
- dialogue_text: {
- name: "对话文本评估",
- dimensions: [
- {
- id: "answer_relevance",
- name: "回答是否有针对性",
- description: "评估回答内容是否针对问题,是否切中要点",
- category: "accuracy" as const,
- isEnabled: true,
- },
- {
- id: "question_correctness",
- name: "问题是否正确",
- description: "评估问题表述是否清晰、准确、合理",
- category: "quality" as const,
- isEnabled: true,
- },
- {
- id: "answer_independence",
- name: "回答是否独立",
- description: "评估回答是否独立完整,不依赖外部信息",
- category: "completeness" as const,
- isEnabled: true,
- },
- ],
- },
- data_quality: {
- name: "数据质量评估",
- dimensions: [
- {
- id: "data_quality",
- name: "数据质量",
- description: "评估数据的整体质量,包括格式规范性、完整性等",
- category: "quality" as const,
- isEnabled: true,
- },
- {
- id: "label_accuracy",
- name: "标签准确性",
- description: "评估数据标签的准确性和一致性",
- category: "accuracy" as const,
- isEnabled: true,
- },
- {
- id: "data_completeness",
- name: "数据完整性",
- description: "评估数据集的完整性,是否存在缺失数据",
- category: "completeness" as const,
- isEnabled: true,
- },
- ],
- },
+// 预设评估维度配置
+export const presetEvaluationDimensions: EvaluationDimension[] = [
+ {
+ id: "answer_relevance",
+ name: "回答相关性",
+ description: "评估回答内容是否针对问题,是否切中要点",
+ category: "accuracy",
+ isEnabled: true,
+ },
+ {
+ id: "content_quality",
+ name: "内容质量",
+ description: "评估内容的准确性、完整性和可读性",
+ category: "quality",
+ isEnabled: true,
+ },
+ {
+ id: "information_completeness",
+ name: "信息完整性",
+ description: "评估信息是否完整,无缺失关键内容",
+ category: "completeness",
+ isEnabled: true,
+ },
+ {
+ id: "language_fluency",
+ name: "语言流畅性",
+ description: "评估语言表达是否流畅自然",
+ category: "quality",
+ isEnabled: true,
+ },
+ {
+ id: "factual_accuracy",
+ name: "事实准确性",
+ description: "评估内容中事实信息的准确性",
+ category: "accuracy",
+ isEnabled: true,
+ },
+]
+
+
+export const sliceOperators: SliceOperator[] = [
+ {
+ id: "paragraph-split",
+ name: "段落分割",
+ description: "按段落自然分割文档",
+ type: "text",
+ icon: "📄",
+ params: { minLength: 50, maxLength: 1000 },
+ },
+ {
+ id: "sentence-split",
+ name: "句子分割",
+ description: "按句子边界分割文档",
+ type: "text",
+ icon: "📝",
+ params: { maxSentences: 5, overlap: 1 },
+ },
+ {
+ id: "semantic-split",
+ name: "语义分割",
+ description: "基于语义相似度智能分割",
+ type: "semantic",
+ icon: "🧠",
+ params: { threshold: 0.7, windowSize: 3 },
+ },
+ {
+ id: "length-split",
+ name: "长度分割",
+ description: "按固定字符长度分割",
+ type: "text",
+ icon: "📏",
+ params: { chunkSize: 512, overlap: 50 },
+ },
+ {
+ id: "structure-split",
+ name: "结构化分割",
+ description: "按文档结构(标题、章节)分割",
+ type: "structure",
+ icon: "🏗️",
+ params: { preserveHeaders: true, minSectionLength: 100 },
+ },
+ {
+ id: "table-extract",
+ name: "表格提取",
+ description: "提取并单独处理表格内容",
+ type: "structure",
+ icon: "📊",
+ params: { includeHeaders: true, mergeRows: false },
+ },
+ {
+ id: "code-extract",
+ name: "代码提取",
+ description: "识别并提取代码块",
+ type: "custom",
+ icon: "💻",
+ params: { languages: ["python", "javascript", "sql"], preserveIndentation: true },
+ },
+ {
+ id: "qa-extract",
+ name: "问答提取",
+ description: "自动识别问答格式内容",
+ type: "semantic",
+ icon: "❓",
+ params: { confidenceThreshold: 0.8, generateAnswers: true },
+ },
+]
+
+
+export const mockTasks: EvaluationTask[] = [
+ {
+ id: "1",
+ name: "客服对话数据质量评估",
+ datasetId: "1",
+ datasetName: "客服对话数据集",
+ evaluationType: "model",
+ status: "completed",
+ score: 85,
+ progress: 100,
+ createdAt: "2024-01-15 14:30",
+ completedAt: "2024-01-15 14:45",
+ description: "评估客服对话数据的质量,包括对话完整性、回复准确性等维度",
+ dimensions: ["answer_relevance", "content_quality", "information_completeness"],
+ customDimensions: [],
+ sliceConfig: {
+ threshold: 0.8,
+ sampleCount: 100,
+ method: "语义分割",
+ },
+ modelConfig: {
+ url: "https://api.openai.com/v1/chat/completions",
+ apiKey: "sk-***",
+ prompt: "请从数据质量、标签准确性、标注一致性三个维度评估这个客服对话数据集...",
+ temperature: 0.3,
+ maxTokens: 2000,
+ },
+ metrics: {
+ accuracy: 88,
+ completeness: 92,
+ consistency: 78,
+ relevance: 85,
+ },
+ issues: [
+ { type: "重复数据", count: 23, severity: "medium" },
+ { type: "格式错误", count: 5, severity: "high" },
+ { type: "内容不完整", count: 12, severity: "low" },
+ ],
+ },
+ {
+ id: "2",
+ name: "产品评论人工评估",
+ datasetId: "2",
+ datasetName: "产品评论数据集",
+ evaluationType: "manual",
+ status: "pending",
+ progress: 0,
+ createdAt: "2024-01-15 15:20",
+ description: "人工评估产品评论数据的情感标注准确性",
+ dimensions: ["content_quality", "factual_accuracy"],
+ customDimensions: [
+ {
+ id: "custom_1",
+ name: "情感极性准确性",
+ description: "评估情感标注的极性(正面/负面/中性)准确性",
+ category: "custom",
+ isCustom: true,
+ isEnabled: true,
+ },
+ ],
+ sliceConfig: {
+ threshold: 0.7,
+ sampleCount: 50,
+ method: "段落分割",
+ },
+ metrics: {
+ accuracy: 0,
+ completeness: 0,
+ consistency: 0,
+ relevance: 0,
+ },
+ issues: [],
+ },
+ {
+ id: "3",
+ name: "新闻分类数据评估",
+ datasetId: "4",
+ datasetName: "新闻分类数据集",
+ evaluationType: "manual",
+ status: "running",
+ progress: 65,
+ createdAt: "2024-01-15 16:10",
+ description: "人工评估新闻分类数据集的标注质量",
+ dimensions: ["content_quality", "information_completeness", "factual_accuracy"],
+ customDimensions: [],
+ sliceConfig: {
+ threshold: 0.9,
+ sampleCount: 80,
+ method: "句子分割",
+ },
+ metrics: {
+ accuracy: 82,
+ completeness: 78,
+ consistency: 85,
+ relevance: 80,
+ },
+ issues: [{ type: "标注不一致", count: 15, severity: "medium" }],
+ },
+]
+
+// 模拟QA对数据
+export const mockQAPairs: QAPair[] = [
+ {
+ id: "qa_1",
+ question: "这个产品的退货政策是什么?",
+ answer: "我们提供7天无理由退货服务,商品需要保持原包装完整。",
+ sliceId: "slice_1",
+ score: 4.5,
+ feedback: "回答准确且完整",
+ },
+ {
+ id: "qa_2",
+ question: "如何联系客服?",
+ answer: "您可以通过在线客服、电话400-123-4567或邮箱service@company.com联系我们。",
+ sliceId: "slice_2",
+ score: 5.0,
+ feedback: "提供了多种联系方式,非常全面",
+ },
+ {
+ id: "qa_3",
+ question: "配送时间需要多久?",
+ answer: "一般情况下,我们会在1-3个工作日内发货,配送时间根据地区不同为2-7天。",
+ sliceId: "slice_3",
+ score: 4.0,
+ feedback: "时间范围说明清楚",
+ },
+]
+// 评估维度模板配置
+export const evaluationTemplates = {
+ dialogue_text: {
+ name: "对话文本评估",
+ dimensions: [
+ {
+ id: "answer_relevance",
+ name: "回答是否有针对性",
+ description: "评估回答内容是否针对问题,是否切中要点",
+ category: "accuracy" as const,
+ isEnabled: true,
+ },
+ {
+ id: "question_correctness",
+ name: "问题是否正确",
+ description: "评估问题表述是否清晰、准确、合理",
+ category: "quality" as const,
+ isEnabled: true,
+ },
+ {
+ id: "answer_independence",
+ name: "回答是否独立",
+ description: "评估回答是否独立完整,不依赖外部信息",
+ category: "completeness" as const,
+ isEnabled: true,
+ },
+ ],
+ },
+ data_quality: {
+ name: "数据质量评估",
+ dimensions: [
+ {
+ id: "data_quality",
+ name: "数据质量",
+ description: "评估数据的整体质量,包括格式规范性、完整性等",
+ category: "quality" as const,
+ isEnabled: true,
+ },
+ {
+ id: "label_accuracy",
+ name: "标签准确性",
+ description: "评估数据标签的准确性和一致性",
+ category: "accuracy" as const,
+ isEnabled: true,
+ },
+ {
+ id: "data_completeness",
+ name: "数据完整性",
+ description: "评估数据集的完整性,是否存在缺失数据",
+ category: "completeness" as const,
+ isEnabled: true,
+ },
+ ],
+ },
}
\ No newline at end of file
diff --git a/frontend/src/mock/knowledgeBase.tsx b/frontend/src/mock/knowledgeBase.tsx
index dbfc968d0..b2b9fb59f 100644
--- a/frontend/src/mock/knowledgeBase.tsx
+++ b/frontend/src/mock/knowledgeBase.tsx
@@ -1,254 +1,254 @@
-export const mockChunks = Array.from({ length: 23 }, (_, i) => ({
- id: i + 1,
- content: `这是第 ${
- i + 1
- } 个文档分块的内容示例。在实际应用中,这里会显示从原始文档中提取和分割的具体文本内容。用户可以在这里查看和编辑分块的内容,确保知识库的质量和准确性。这个分块包含了重要的业务信息和技术细节,需要仔细维护以确保检索的准确性。`,
- position: i + 1,
- tokens: Math.floor(Math.random() * 200) + 100,
- embedding: Array.from({ length: 1536 }, () => Math.random() - 0.5),
- similarity: (Math.random() * 0.3 + 0.7).toFixed(3),
- createdAt: "2024-01-22 10:35",
- updatedAt: "2024-01-22 10:35",
- vectorId: `vec_${i + 1}_${Math.random().toString(36).substr(2, 9)}`,
- sliceOperator: ["semantic-split", "paragraph-split", "table-extract"][
- Math.floor(Math.random() * 3)
- ],
- parentChunkId: i > 0 ? Math.floor(Math.random() * i) + 1 : undefined,
- metadata: {
- source: "API文档.pdf",
- page: Math.floor(i / 5) + 1,
- section: `第${Math.floor(i / 3) + 1}章`,
- },
-}));
-
-export const mockQAPairs = [
- {
- id: 1,
- question: "什么是API文档的主要用途?",
- answer:
- "API文档的主要用途是为开发者提供详细的接口说明,包括请求参数、响应格式和使用示例.",
- },
- {
- id: 2,
- question: "如何正确使用这个API?",
- answer:
- "使用API时需要先获取访问令牌,然后按照文档中的格式发送请求,注意处理错误响应.",
- },
-];
-
-export const sliceOperators: SliceOperator[] = [
- {
- id: "paragraph-split",
- name: "段落分割",
- description: "按段落自然分割文档",
- type: "text",
- icon: "📄",
- params: { minLength: 50, maxLength: 1000 },
- },
- {
- id: "sentence-split",
- name: "句子分割",
- description: "按句子边界分割文档",
- type: "text",
- icon: "📝",
- params: { maxSentences: 5, overlap: 1 },
- },
- {
- id: "semantic-split",
- name: "语义分割",
- description: "基于语义相似度智能分割",
- type: "semantic",
- icon: "🧠",
- params: { threshold: 0.7, windowSize: 3 },
- },
- {
- id: "length-split",
- name: "长度分割",
- description: "按固定字符长度分割",
- type: "text",
- icon: "📏",
- params: { chunkSize: 512, overlap: 50 },
- },
- {
- id: "structure-split",
- name: "结构化分割",
- description: "按文档结构(标题、章节)分割",
- type: "structure",
- icon: "🏗️",
- params: { preserveHeaders: true, minSectionLength: 100 },
- },
- {
- id: "table-extract",
- name: "表格提取",
- description: "提取并单独处理表格内容",
- type: "structure",
- icon: "📊",
- params: { includeHeaders: true, mergeRows: false },
- },
- {
- id: "code-extract",
- name: "代码提取",
- description: "识别并提取代码块",
- type: "custom",
- icon: "💻",
- params: {
- languages: ["python", "javascript", "sql"],
- preserveIndentation: true,
- },
- },
- {
- id: "qa-extract",
- name: "问答提取",
- description: "自动识别问答格式内容",
- type: "semantic",
- icon: "❓",
- params: { confidenceThreshold: 0.8, generateAnswers: true },
- },
-];
-
-export const vectorDatabases = [
- {
- id: "pinecone",
- name: "Pinecone",
- description: "云端向量数据库,高性能检索",
- },
- {
- id: "weaviate",
- name: "Weaviate",
- description: "开源向量数据库,支持多模态",
- },
- { id: "qdrant", name: "Qdrant", description: "高性能向量搜索引擎" },
- { id: "chroma", name: "ChromaDB", description: "轻量级向量数据库" },
- { id: "milvus", name: "Milvus", description: "分布式向量数据库" },
- { id: "faiss", name: "FAISS", description: "Facebook AI 相似性搜索库" },
-];
-
-export const mockKnowledgeBases: KnowledgeBase[] = [
- {
- id: 1,
- name: "产品技术文档库",
- description:
- "包含所有产品相关的技术文档和API说明,支持多种格式文档的智能解析和向量化处理",
- type: "unstructured",
- status: "ready",
- fileCount: 45,
- chunkCount: 1250,
- vectorCount: 1250,
- size: "2.3 GB",
- progress: 100,
- createdAt: "2024-01-15",
- lastUpdated: "2024-01-22",
- vectorDatabase: "pinecone",
- config: {
- embeddingModel: "text-embedding-3-large",
- llmModel: "gpt-4o",
- chunkSize: 512,
- overlap: 50,
- sliceMethod: "semantic",
- enableQA: true,
- vectorDimension: 1536,
- sliceOperators: ["semantic-split", "paragraph-split", "table-extract"],
- },
- files: [
- {
- id: 1,
- name: "API文档.pdf",
- type: "pdf",
- size: "2.5 MB",
- status: "completed",
- chunkCount: 156,
- progress: 100,
- uploadedAt: "2024-01-15",
- source: "upload",
- vectorizationStatus: "completed",
- },
- {
- id: 2,
- name: "用户手册.docx",
- type: "docx",
- size: "1.8 MB",
- status: "disabled",
- chunkCount: 89,
- progress: 65,
- uploadedAt: "2024-01-22",
- source: "dataset",
- datasetId: "dataset-1",
- vectorizationStatus: "failed",
- },
- ],
- vectorizationHistory: [
- {
- id: 1,
- timestamp: "2024-01-22 14:30:00",
- operation: "create",
- fileId: 1,
- fileName: "API文档.pdf",
- chunksProcessed: 156,
- vectorsGenerated: 156,
- status: "success",
- duration: "2m 15s",
- config: {
- embeddingModel: "text-embedding-3-large",
- chunkSize: 512,
- sliceMethod: "semantic",
- },
- },
- {
- id: 2,
- timestamp: "2024-01-22 15:45:00",
- operation: "update",
- fileId: 2,
- fileName: "用户手册.docx",
- chunksProcessed: 89,
- vectorsGenerated: 0,
- status: "failed",
- duration: "0m 45s",
- config: {
- embeddingModel: "text-embedding-3-large",
- chunkSize: 512,
- sliceMethod: "semantic",
- },
- error: "向量化服务连接超时",
- },
- ],
- },
- {
- id: 2,
- name: "FAQ结构化知识库",
- description: "客服常见问题的结构化问答对,支持快速检索和智能匹配",
- type: "structured",
- status: "vectorizing",
- fileCount: 12,
- chunkCount: 890,
- vectorCount: 750,
- size: "156 MB",
- progress: 75,
- createdAt: "2024-01-20",
- lastUpdated: "2024-01-23",
- vectorDatabase: "weaviate",
- config: {
- embeddingModel: "text-embedding-ada-002",
- chunkSize: 256,
- overlap: 0,
- sliceMethod: "paragraph",
- enableQA: false,
- vectorDimension: 1536,
- sliceOperators: ["qa-extract", "paragraph-split"],
- },
- files: [
- {
- id: 3,
- name: "FAQ模板.xlsx",
- type: "xlsx",
- size: "450 KB",
- status: "vectorizing",
- chunkCount: 234,
- progress: 75,
- uploadedAt: "2024-01-20",
- source: "upload",
- vectorizationStatus: "processing",
- },
- ],
- vectorizationHistory: [],
- },
-];
+export const mockChunks = Array.from({ length: 23 }, (_, i) => ({
+ id: i + 1,
+ content: `这是第 ${
+ i + 1
+ } 个文档分块的内容示例。在实际应用中,这里会显示从原始文档中提取和分割的具体文本内容。用户可以在这里查看和编辑分块的内容,确保知识库的质量和准确性。这个分块包含了重要的业务信息和技术细节,需要仔细维护以确保检索的准确性。`,
+ position: i + 1,
+ tokens: Math.floor(Math.random() * 200) + 100,
+ embedding: Array.from({ length: 1536 }, () => Math.random() - 0.5),
+ similarity: (Math.random() * 0.3 + 0.7).toFixed(3),
+ createdAt: "2024-01-22 10:35",
+ updatedAt: "2024-01-22 10:35",
+ vectorId: `vec_${i + 1}_${Math.random().toString(36).substr(2, 9)}`,
+ sliceOperator: ["semantic-split", "paragraph-split", "table-extract"][
+ Math.floor(Math.random() * 3)
+ ],
+ parentChunkId: i > 0 ? Math.floor(Math.random() * i) + 1 : undefined,
+ metadata: {
+ source: "API文档.pdf",
+ page: Math.floor(i / 5) + 1,
+ section: `第${Math.floor(i / 3) + 1}章`,
+ },
+}));
+
+export const mockQAPairs = [
+ {
+ id: 1,
+ question: "什么是API文档的主要用途?",
+ answer:
+ "API文档的主要用途是为开发者提供详细的接口说明,包括请求参数、响应格式和使用示例.",
+ },
+ {
+ id: 2,
+ question: "如何正确使用这个API?",
+ answer:
+ "使用API时需要先获取访问令牌,然后按照文档中的格式发送请求,注意处理错误响应.",
+ },
+];
+
+export const sliceOperators: SliceOperator[] = [
+ {
+ id: "paragraph-split",
+ name: "段落分割",
+ description: "按段落自然分割文档",
+ type: "text",
+ icon: "📄",
+ params: { minLength: 50, maxLength: 1000 },
+ },
+ {
+ id: "sentence-split",
+ name: "句子分割",
+ description: "按句子边界分割文档",
+ type: "text",
+ icon: "📝",
+ params: { maxSentences: 5, overlap: 1 },
+ },
+ {
+ id: "semantic-split",
+ name: "语义分割",
+ description: "基于语义相似度智能分割",
+ type: "semantic",
+ icon: "🧠",
+ params: { threshold: 0.7, windowSize: 3 },
+ },
+ {
+ id: "length-split",
+ name: "长度分割",
+ description: "按固定字符长度分割",
+ type: "text",
+ icon: "📏",
+ params: { chunkSize: 512, overlap: 50 },
+ },
+ {
+ id: "structure-split",
+ name: "结构化分割",
+ description: "按文档结构(标题、章节)分割",
+ type: "structure",
+ icon: "🏗️",
+ params: { preserveHeaders: true, minSectionLength: 100 },
+ },
+ {
+ id: "table-extract",
+ name: "表格提取",
+ description: "提取并单独处理表格内容",
+ type: "structure",
+ icon: "📊",
+ params: { includeHeaders: true, mergeRows: false },
+ },
+ {
+ id: "code-extract",
+ name: "代码提取",
+ description: "识别并提取代码块",
+ type: "custom",
+ icon: "💻",
+ params: {
+ languages: ["python", "javascript", "sql"],
+ preserveIndentation: true,
+ },
+ },
+ {
+ id: "qa-extract",
+ name: "问答提取",
+ description: "自动识别问答格式内容",
+ type: "semantic",
+ icon: "❓",
+ params: { confidenceThreshold: 0.8, generateAnswers: true },
+ },
+];
+
+export const vectorDatabases = [
+ {
+ id: "pinecone",
+ name: "Pinecone",
+ description: "云端向量数据库,高性能检索",
+ },
+ {
+ id: "weaviate",
+ name: "Weaviate",
+ description: "开源向量数据库,支持多模态",
+ },
+ { id: "qdrant", name: "Qdrant", description: "高性能向量搜索引擎" },
+ { id: "chroma", name: "ChromaDB", description: "轻量级向量数据库" },
+ { id: "milvus", name: "Milvus", description: "分布式向量数据库" },
+ { id: "faiss", name: "FAISS", description: "Facebook AI 相似性搜索库" },
+];
+
+export const mockKnowledgeBases: KnowledgeBase[] = [
+ {
+ id: 1,
+ name: "产品技术文档库",
+ description:
+ "包含所有产品相关的技术文档和API说明,支持多种格式文档的智能解析和向量化处理",
+ type: "unstructured",
+ status: "ready",
+ fileCount: 45,
+ chunkCount: 1250,
+ vectorCount: 1250,
+ size: "2.3 GB",
+ progress: 100,
+ createdAt: "2024-01-15",
+ lastUpdated: "2024-01-22",
+ vectorDatabase: "pinecone",
+ config: {
+ embeddingModel: "text-embedding-3-large",
+ llmModel: "gpt-4o",
+ chunkSize: 512,
+ overlap: 50,
+ sliceMethod: "semantic",
+ enableQA: true,
+ vectorDimension: 1536,
+ sliceOperators: ["semantic-split", "paragraph-split", "table-extract"],
+ },
+ files: [
+ {
+ id: 1,
+ name: "API文档.pdf",
+ type: "pdf",
+ size: "2.5 MB",
+ status: "completed",
+ chunkCount: 156,
+ progress: 100,
+ uploadedAt: "2024-01-15",
+ source: "upload",
+ vectorizationStatus: "completed",
+ },
+ {
+ id: 2,
+ name: "用户手册.docx",
+ type: "docx",
+ size: "1.8 MB",
+ status: "disabled",
+ chunkCount: 89,
+ progress: 65,
+ uploadedAt: "2024-01-22",
+ source: "dataset",
+ datasetId: "dataset-1",
+ vectorizationStatus: "failed",
+ },
+ ],
+ vectorizationHistory: [
+ {
+ id: 1,
+ timestamp: "2024-01-22 14:30:00",
+ operation: "create",
+ fileId: 1,
+ fileName: "API文档.pdf",
+ chunksProcessed: 156,
+ vectorsGenerated: 156,
+ status: "success",
+ duration: "2m 15s",
+ config: {
+ embeddingModel: "text-embedding-3-large",
+ chunkSize: 512,
+ sliceMethod: "semantic",
+ },
+ },
+ {
+ id: 2,
+ timestamp: "2024-01-22 15:45:00",
+ operation: "update",
+ fileId: 2,
+ fileName: "用户手册.docx",
+ chunksProcessed: 89,
+ vectorsGenerated: 0,
+ status: "failed",
+ duration: "0m 45s",
+ config: {
+ embeddingModel: "text-embedding-3-large",
+ chunkSize: 512,
+ sliceMethod: "semantic",
+ },
+ error: "向量化服务连接超时",
+ },
+ ],
+ },
+ {
+ id: 2,
+ name: "FAQ结构化知识库",
+ description: "客服常见问题的结构化问答对,支持快速检索和智能匹配",
+ type: "structured",
+ status: "vectorizing",
+ fileCount: 12,
+ chunkCount: 890,
+ vectorCount: 750,
+ size: "156 MB",
+ progress: 75,
+ createdAt: "2024-01-20",
+ lastUpdated: "2024-01-23",
+ vectorDatabase: "weaviate",
+ config: {
+ embeddingModel: "text-embedding-ada-002",
+ chunkSize: 256,
+ overlap: 0,
+ sliceMethod: "paragraph",
+ enableQA: false,
+ vectorDimension: 1536,
+ sliceOperators: ["qa-extract", "paragraph-split"],
+ },
+ files: [
+ {
+ id: 3,
+ name: "FAQ模板.xlsx",
+ type: "xlsx",
+ size: "450 KB",
+ status: "vectorizing",
+ chunkCount: 234,
+ progress: 75,
+ uploadedAt: "2024-01-20",
+ source: "upload",
+ vectorizationStatus: "processing",
+ },
+ ],
+ vectorizationHistory: [],
+ },
+];
diff --git a/frontend/src/mock/mock-apis.cjs b/frontend/src/mock/mock-apis.cjs
index 8b1a02bfa..c219608aa 100644
--- a/frontend/src/mock/mock-apis.cjs
+++ b/frontend/src/mock/mock-apis.cjs
@@ -1,167 +1,167 @@
-const { addMockPrefix } = require("./mock-core/util.cjs");
-
-const MockAPI = {
- // 数据归集接口
- queryTasksUsingGet: "/data-collection/tasks", // 获取数据源任务列表
- createTaskUsingPost: "/data-collection/tasks", // 创建数据源任务
- queryTaskByIdUsingGet: "/data-collection/tasks/:id", // 根据ID获取数据源任务详情
- updateTaskByIdUsingPut: "/data-collection/tasks/:id", // 更新数据源任务
- queryDataXTemplatesUsingGet: "/data-collection/templates", // 获取DataX数据源模板列表
- deleteTaskByIdUsingDelete: "/data-collection/tasks/:id", // 删除数据源任务
- executeTaskByIdUsingPost: "/data-collection/tasks/:id/execute", // 执行数据源任务
- stopTaskByIdUsingPost: "/data-collection/tasks/:id/stop", // 停止数据源任务
- queryExecutionLogUsingPost: "/data-collection/executions", // 获取任务执行日志
- queryExecutionLogByIdUsingGet: "/data-collection/executions/:id", // 获取任务执行日志详情
- queryCollectionStatisticsUsingGet: "/data-collection/monitor/statistics", // 获取数据归集统计信息
-
- // 数据管理接口
- queryDatasetsUsingGet: "/data-management/datasets", // 获取数据集列表
- createDatasetUsingPost: "/data-management/datasets", // 创建数据集
- queryDatasetByIdUsingGet: "/data-management/datasets/:id", // 根据ID获取数据集详情
- updateDatasetByIdUsingPut: "/data-management/datasets/:id", // 更新数据集
- deleteDatasetByIdUsingDelete: "/data-management/datasets/:id", // 删除数据集
- queryFilesUsingGet: "/data-management/datasets/:id/files", // 获取数据集文件列表
- uploadFileUsingPost: "/data-management/datasets/:id/files", // 添加数据集文件
- queryFileByIdUsingGet: "/data-management/datasets/:id/files/:fileId", // 获取数据集文件详情
- deleteFileByIdUsingDelete: "/data-management/datasets/:id/files/:fileId", // 删除数据集文件
- downloadFileByIdUsingGet:
- "/data-management/datasets/:id/files/:fileId/download", // 下载文件
- queryDatasetTypesUsingGet: "/data-management/dataset-types", // 获取数据集类型列表
- queryTagsUsingGet: "/data-management/tags", // 获取数据集标签列表
- createTagUsingPost: "/data-management/tags", // 创建数据集标签
- updateTagUsingPost: "/data-management/tags", // 更新数据集标签
- deleteTagUsingPost: "/data-management/tags", // 删除数据集标签
- queryDatasetStatisticsUsingGet: "/data-management/datasets/statistics", // 获取数据集统计信息
- preUploadFileUsingPost: "/data-management/datasets/:id/upload/pre-upload", // 预上传文件
- cancelUploadUsingPut: "/data-management/datasets/upload/cancel-upload/:id", // 取消上传
- uploadFileChunkUsingPost: "/data-management/datasets/:id/upload/chunk", // 上传切片
-
- // 数据清洗接口
- queryCleaningTasksUsingGet: "/cleaning/tasks", // 获取清洗任务列表
- createCleaningTaskUsingPost: "/cleaning/tasks", // 创建清洗任务
- queryCleaningTaskByIdUsingGet: "/cleaning/tasks/:taskId", // 根据ID获取清洗任务详情
- deleteCleaningTaskByIdUsingDelete: "/cleaning/tasks/:taskId", // 删除清洗任务
- executeCleaningTaskUsingPost: "/cleaning/tasks/:taskId/execute", // 执行清洗任务
- stopCleaningTaskUsingPost: "/cleaning/tasks/:taskId/stop", // 停止清洗任务
- queryCleaningTemplatesUsingGet: "/cleaning/templates", // 获取清洗模板列表
- createCleaningTemplateUsingPost: "/cleaning/templates", // 创建清洗模板
- queryCleaningTemplateByIdUsingGet: "/cleaning/templates/:templateId", // 根据ID获取清洗模板详情
- updateCleaningTemplateByIdUsingPut: "/cleaning/templates/:templateId", // 根据ID更新清洗模板详情
- deleteCleaningTemplateByIdUsingDelete: "/cleaning/templates/:templateId", // 删除清洗模板
-
- // 数据标注接口
- queryAnnotationTasksUsingGet: "/project/mappings/list", // 获取标注任务列表
- createAnnotationTaskUsingPost: "/project/create", // 创建标注任务
- syncAnnotationTaskByIdUsingPost: "/project/sync", // 同步标注任务
- deleteAnnotationTaskByIdUsingDelete: "/project/mappings", // 删除标注任务
- queryAnnotationTaskByIdUsingGet: "/annotation/tasks/:taskId", // 根据ID获取标注任务详情
- executeAnnotationTaskByIdUsingPost: "/annotation/tasks/:taskId/execute", // 执行标注任务
- stopAnnotationTaskByIdUsingPost: "/annotation/tasks/:taskId/stop", // 停止标注任务
- queryAnnotationDataUsingGet: "/annotation/data", // 获取标注数据列表
- submitAnnotationUsingPost: "/annotation/submit/:id", // 提交标注
- updateAnnotationUsingPut: "/annotation/update/:id", // 根据ID更新标注
- deleteAnnotationUsingDelete: "/annotation/delete/:id", // 根据ID删除标注
- startAnnotationTaskUsingPost: "/annotation/start/:taskId", // 开始标注任务
- pauseAnnotationTaskUsingPost: "/annotation/pause/:taskId", // 暂停标注任务
- resumeAnnotationTaskUsingPost: "/annotation/resume/:taskId", // 恢复标注任务
- completeAnnotationTaskUsingPost: "/annotation/complete/:taskId", // 完成标注任务
- getAnnotationTaskStatisticsUsingGet: "/annotation/statistics/:taskId", // 获取标注任务统计信息
- getAnnotationStatisticsUsingGet: "/annotation/statistics", // 获取标注统计信息
- queryAnnotationTemplatesUsingGet: "/annotation/templates", // 获取标注模板列表
- createAnnotationTemplateUsingPost: "/annotation/templates", // 创建标注模板
- queryAnnotationTemplateByIdUsingGet: "/annotation/templates/:templateId", // 根据ID获取标注模板详情
- queryAnnotatorsUsingGet: "/annotation/annotators", // 获取标注者列表
- assignAnnotatorUsingPost: "/annotation/annotators/:annotatorId", // 分配标注者
-
- // 数据合成接口
- querySynthesisJobsUsingGet: "/synthesis/jobs", // 获取合成任务列表
- createSynthesisJobUsingPost: "/synthesis/jobs/create", // 创建合成任务
- querySynthesisJobByIdUsingGet: "/synthesis/jobs/:jobId", // 根据ID获取合成任务详情
- updateSynthesisJobByIdUsingPut: "/synthesis/jobs/:jobId", // 更新合成任务
- deleteSynthesisJobByIdUsingDelete: "/synthesis/jobs/:jobId", // 删除合成任务
- executeSynthesisJobUsingPost: "/synthesis/jobs/execute/:jobId", // 执行合成任务
- stopSynthesisJobByIdUsingPost: "/synthesis/jobs/stop/:jobId", // 停止合成任务
- querySynthesisTemplatesUsingGet: "/synthesis/templates", // 获取合成模板列表
- createSynthesisTemplateUsingPost: "/synthesis/templates/create", // 创建合成模板
- querySynthesisTemplateByIdUsingGet: "/synthesis/templates/:templateId", // 根据ID获取合成模板详情
- updateSynthesisTemplateByIdUsingPut: "/synthesis/templates/:templateId", // 更新合成模板
- deleteSynthesisTemplateByIdUsingDelete: "/synthesis/templates/:templateId", // 删除合成模板
- queryInstructionTemplatesUsingPost: "/synthesis/templates", // 获取指令模板列表
- createInstructionTemplateUsingPost: "/synthesis/templates/create", // 创建指令模板
- queryInstructionTemplateByIdUsingGet: "/synthesis/templates/:templateId", // 根据ID获取指令模板详情
- deleteInstructionTemplateByIdUsingDelete: "/synthesis/templates/:templateId", // 删除指令模板
- instructionTuningUsingPost: "/synthesis/instruction-tuning", // 指令微调
- cotDistillationUsingPost: "/synthesis/cot-distillation", // Cot蒸馏
-
- // 数据配比接口
- createRatioTaskUsingPost: "/synthesis/ratio-task", // 创建配比任务
- queryRatioTasksUsingGet: "/synthesis/ratio-task", // 获取配比任务列表
- queryRatioTaskByIdUsingGet: "/synthesis/ratio-task/:taskId", // 根据ID获取配比任务详情
- deleteRatioTaskByIdUsingDelete: "/synthesis/ratio-task/:taskId", // 删除配比任务
- updateRatioTaskByIdUsingPut: "/synthesis/ratio-task/:taskId", // 更新配比任务
- executeRatioTaskByIdUsingPost: "/synthesis/ratio-task/:taskId/execute", // 执行配比任务
- stopRatioTaskByIdUsingPost: "/synthesis/ratio-task/:taskId/stop", // 停止配比任务
- queryRatioJobStatusUsingGet: "/synthesis/ratio-task/:taskId/status", // 获取配比任务状态
- queryRatioModelsUsingGet: "/synthesis/ratio-models", // 获取配比模型列表
-
- // 数据评测接口
- queryEvaluationTasksUsingPost: "/evaluation/tasks", // 获取评测任务列表
- createEvaluationTaskUsingPost: "/evaluation/tasks/create", // 创建评测任务
- queryEvaluationTaskByIdUsingGet: "/evaluation/tasks/:taskId", // 根据ID获取评测任务详情
- updateEvaluationTaskByIdUsingPut: "/evaluation/tasks/:taskId", // 更新评测任务
- deleteEvaluationTaskByIdUsingDelete: "/evaluation/tasks/:taskId", // 删除评测任务
- executeEvaluationTaskByIdUsingPost: "/evaluation/tasks/:taskId/execute", // 执行评测任务
- stopEvaluationTaskByIdUsingPost: "/evaluation/tasks/:taskId/stop", // 停止评测任务
- queryEvaluationReportsUsingPost: "/evaluation/reports", // 获取评测报告列表
- queryEvaluationReportByIdUsingGet: "/evaluation/reports/:reportId", // 根据ID获取评测报告详情
- manualEvaluateUsingPost: "/evaluation/manual-evaluate", // 人工评测
- queryEvaluationStatisticsUsingGet: "/evaluation/statistics", // 获取评测统计信息
- evaluateDataQualityUsingPost: "/evaluation/data-quality", // 数据质量评测
- getQualityEvaluationByIdUsingGet: "/evaluation/data-quality/:id", // 根据ID获取数据质量评测详情
- evaluateCompatibilityUsingPost: "/evaluation/compatibility", // 兼容性评测
- evaluateValueUsingPost: "/evaluation/value", // 价值评测
- queryEvaluationReportsUsingGet: "/evaluation/reports", // 获取评测报告列表(简化版)
- getEvaluationReportByIdUsingGet: "/evaluation/reports/:reportId", // 根据ID获取评测报告详情(简化版)
- exportEvaluationReportUsingGet: "/evaluation/reports/:reportId/export", // 导出评测报告
- batchEvaluationUsingPost: "/evaluation/batch-evaluate", // 批量评测
-
- // 知识生成接口
- queryKnowledgeBasesUsingPost: "/knowledge-base/list", // 获取知识库列表
- createKnowledgeBaseUsingPost: "/knowledge-base/create", // 创建知识库
- queryKnowledgeBaseByIdUsingGet: "/knowledge-base/:baseId", // 根据ID获取知识库详情
- updateKnowledgeBaseByIdUsingPut: "/knowledge-base/:baseId", // 更新知识库
- deleteKnowledgeBaseByIdUsingDelete: "/knowledge-base/:baseId", // 删除知识库
- addKnowledgeBaseFilesUsingPost: "/knowledge-base/:baseId/files", // 添加文件到知识库
- queryKnowledgeBaseFilesGet: "/knowledge-base/:baseId/files", // 根据ID获取知识生成文件列表
- queryKnowledgeBaseFilesByIdUsingGet:
- "/knowledge-base/:baseId/files/:fileId", // 根据ID获取知识生成文件详情
- deleteKnowledgeBaseTaskByIdUsingDelete: "/knowledge-base/:baseId/files/:id", // 删除知识生成文件
-
- // 算子市场
- queryOperatorsUsingPost: "/operators/list", // 获取算子列表
- queryCategoryTreeUsingGet: "/categories/tree", // 获取算子分类树
- queryOperatorByIdUsingGet: "/operators/:id", // 根据ID获取算子详情
- createOperatorUsingPost: "/operators/create", // 创建算子
- updateOperatorByIdUsingPut: "/operators/:id", // 更新算子
- uploadOperatorUsingPost: "/operators/upload", // 上传算子
- uploadFileChunkUsingPost: "/operators/upload/chunk", // 上传切片
- preUploadOperatorUsingPost: "/operators/upload/pre-upload", // 预上传文件
- cancelUploadOperatorUsingPut: "/operators/upload/cancel-upload", // 取消上传
-
- createLabelUsingPost: "/operators/labels", // 创建算子标签
- queryLabelsUsingGet: "/labels", // 获取算子标签列表
- deleteLabelsUsingDelete: "/labels", // 删除算子标签
- updateLabelByIdUsingPut: "/labels/:labelId", // 更新算子标签
- deleteOperatorByIdUsingDelete: "/operators/:operatorId", // 删除算子
- publishOperatorUsingPost: "/operators/:operatorId/publish", // 发布算子
- unpublishOperatorUsingPost: "/operators/:operatorId/unpublish", // 下架算子
-
- // 设置接口
- queryModelsUsingGet: "/models/list", // 获取模型列表
- queryProvidersUsingGet: "/models/providers", // 获取模型提供商列表
- createModelUsingPost: "/models/create", // 创建模型
- updateModelUsingPut: "/models/:id", // 更新模型
- deleteModelUsingDelete: "/models/:id", // 删除模型
-};
-
-module.exports = addMockPrefix("/api", MockAPI);
+const { addMockPrefix } = require("./mock-core/util.cjs");
+
+const MockAPI = {
+ // 数据归集接口
+ queryTasksUsingGet: "/data-collection/tasks", // 获取数据源任务列表
+ createTaskUsingPost: "/data-collection/tasks", // 创建数据源任务
+ queryTaskByIdUsingGet: "/data-collection/tasks/:id", // 根据ID获取数据源任务详情
+ updateTaskByIdUsingPut: "/data-collection/tasks/:id", // 更新数据源任务
+ queryDataXTemplatesUsingGet: "/data-collection/templates", // 获取DataX数据源模板列表
+ deleteTaskByIdUsingDelete: "/data-collection/tasks/:id", // 删除数据源任务
+ executeTaskByIdUsingPost: "/data-collection/tasks/:id/execute", // 执行数据源任务
+ stopTaskByIdUsingPost: "/data-collection/tasks/:id/stop", // 停止数据源任务
+ queryExecutionLogUsingPost: "/data-collection/executions", // 获取任务执行日志
+ queryExecutionLogByIdUsingGet: "/data-collection/executions/:id", // 获取任务执行日志详情
+ queryCollectionStatisticsUsingGet: "/data-collection/monitor/statistics", // 获取数据归集统计信息
+
+ // 数据管理接口
+ queryDatasetsUsingGet: "/data-management/datasets", // 获取数据集列表
+ createDatasetUsingPost: "/data-management/datasets", // 创建数据集
+ queryDatasetByIdUsingGet: "/data-management/datasets/:id", // 根据ID获取数据集详情
+ updateDatasetByIdUsingPut: "/data-management/datasets/:id", // 更新数据集
+ deleteDatasetByIdUsingDelete: "/data-management/datasets/:id", // 删除数据集
+ queryFilesUsingGet: "/data-management/datasets/:id/files", // 获取数据集文件列表
+ uploadFileUsingPost: "/data-management/datasets/:id/files", // 添加数据集文件
+ queryFileByIdUsingGet: "/data-management/datasets/:id/files/:fileId", // 获取数据集文件详情
+ deleteFileByIdUsingDelete: "/data-management/datasets/:id/files/:fileId", // 删除数据集文件
+ downloadFileByIdUsingGet:
+ "/data-management/datasets/:id/files/:fileId/download", // 下载文件
+ queryDatasetTypesUsingGet: "/data-management/dataset-types", // 获取数据集类型列表
+ queryTagsUsingGet: "/data-management/tags", // 获取数据集标签列表
+ createTagUsingPost: "/data-management/tags", // 创建数据集标签
+ updateTagUsingPost: "/data-management/tags", // 更新数据集标签
+ deleteTagUsingPost: "/data-management/tags", // 删除数据集标签
+ queryDatasetStatisticsUsingGet: "/data-management/datasets/statistics", // 获取数据集统计信息
+ preUploadFileUsingPost: "/data-management/datasets/:id/upload/pre-upload", // 预上传文件
+ cancelUploadUsingPut: "/data-management/datasets/upload/cancel-upload/:id", // 取消上传
+ uploadFileChunkUsingPost: "/data-management/datasets/:id/upload/chunk", // 上传切片
+
+ // 数据清洗接口
+ queryCleaningTasksUsingGet: "/cleaning/tasks", // 获取清洗任务列表
+ createCleaningTaskUsingPost: "/cleaning/tasks", // 创建清洗任务
+ queryCleaningTaskByIdUsingGet: "/cleaning/tasks/:taskId", // 根据ID获取清洗任务详情
+ deleteCleaningTaskByIdUsingDelete: "/cleaning/tasks/:taskId", // 删除清洗任务
+ executeCleaningTaskUsingPost: "/cleaning/tasks/:taskId/execute", // 执行清洗任务
+ stopCleaningTaskUsingPost: "/cleaning/tasks/:taskId/stop", // 停止清洗任务
+ queryCleaningTemplatesUsingGet: "/cleaning/templates", // 获取清洗模板列表
+ createCleaningTemplateUsingPost: "/cleaning/templates", // 创建清洗模板
+ queryCleaningTemplateByIdUsingGet: "/cleaning/templates/:templateId", // 根据ID获取清洗模板详情
+ updateCleaningTemplateByIdUsingPut: "/cleaning/templates/:templateId", // 根据ID更新清洗模板详情
+ deleteCleaningTemplateByIdUsingDelete: "/cleaning/templates/:templateId", // 删除清洗模板
+
+ // 数据标注接口
+ queryAnnotationTasksUsingGet: "/project/mappings/list", // 获取标注任务列表
+ createAnnotationTaskUsingPost: "/project/create", // 创建标注任务
+ syncAnnotationTaskByIdUsingPost: "/project/sync", // 同步标注任务
+ deleteAnnotationTaskByIdUsingDelete: "/project/mappings", // 删除标注任务
+ queryAnnotationTaskByIdUsingGet: "/annotation/tasks/:taskId", // 根据ID获取标注任务详情
+ executeAnnotationTaskByIdUsingPost: "/annotation/tasks/:taskId/execute", // 执行标注任务
+ stopAnnotationTaskByIdUsingPost: "/annotation/tasks/:taskId/stop", // 停止标注任务
+ queryAnnotationDataUsingGet: "/annotation/data", // 获取标注数据列表
+ submitAnnotationUsingPost: "/annotation/submit/:id", // 提交标注
+ updateAnnotationUsingPut: "/annotation/update/:id", // 根据ID更新标注
+ deleteAnnotationUsingDelete: "/annotation/delete/:id", // 根据ID删除标注
+ startAnnotationTaskUsingPost: "/annotation/start/:taskId", // 开始标注任务
+ pauseAnnotationTaskUsingPost: "/annotation/pause/:taskId", // 暂停标注任务
+ resumeAnnotationTaskUsingPost: "/annotation/resume/:taskId", // 恢复标注任务
+ completeAnnotationTaskUsingPost: "/annotation/complete/:taskId", // 完成标注任务
+ getAnnotationTaskStatisticsUsingGet: "/annotation/statistics/:taskId", // 获取标注任务统计信息
+ getAnnotationStatisticsUsingGet: "/annotation/statistics", // 获取标注统计信息
+ queryAnnotationTemplatesUsingGet: "/annotation/templates", // 获取标注模板列表
+ createAnnotationTemplateUsingPost: "/annotation/templates", // 创建标注模板
+ queryAnnotationTemplateByIdUsingGet: "/annotation/templates/:templateId", // 根据ID获取标注模板详情
+ queryAnnotatorsUsingGet: "/annotation/annotators", // 获取标注者列表
+ assignAnnotatorUsingPost: "/annotation/annotators/:annotatorId", // 分配标注者
+
+ // 数据合成接口
+ querySynthesisJobsUsingGet: "/synthesis/jobs", // 获取合成任务列表
+ createSynthesisJobUsingPost: "/synthesis/jobs/create", // 创建合成任务
+ querySynthesisJobByIdUsingGet: "/synthesis/jobs/:jobId", // 根据ID获取合成任务详情
+ updateSynthesisJobByIdUsingPut: "/synthesis/jobs/:jobId", // 更新合成任务
+ deleteSynthesisJobByIdUsingDelete: "/synthesis/jobs/:jobId", // 删除合成任务
+ executeSynthesisJobUsingPost: "/synthesis/jobs/execute/:jobId", // 执行合成任务
+ stopSynthesisJobByIdUsingPost: "/synthesis/jobs/stop/:jobId", // 停止合成任务
+ querySynthesisTemplatesUsingGet: "/synthesis/templates", // 获取合成模板列表
+ createSynthesisTemplateUsingPost: "/synthesis/templates/create", // 创建合成模板
+ querySynthesisTemplateByIdUsingGet: "/synthesis/templates/:templateId", // 根据ID获取合成模板详情
+ updateSynthesisTemplateByIdUsingPut: "/synthesis/templates/:templateId", // 更新合成模板
+ deleteSynthesisTemplateByIdUsingDelete: "/synthesis/templates/:templateId", // 删除合成模板
+ queryInstructionTemplatesUsingPost: "/synthesis/templates", // 获取指令模板列表
+ createInstructionTemplateUsingPost: "/synthesis/templates/create", // 创建指令模板
+ queryInstructionTemplateByIdUsingGet: "/synthesis/templates/:templateId", // 根据ID获取指令模板详情
+ deleteInstructionTemplateByIdUsingDelete: "/synthesis/templates/:templateId", // 删除指令模板
+ instructionTuningUsingPost: "/synthesis/instruction-tuning", // 指令微调
+ cotDistillationUsingPost: "/synthesis/cot-distillation", // Cot蒸馏
+
+ // 数据配比接口
+ createRatioTaskUsingPost: "/synthesis/ratio-task", // 创建配比任务
+ queryRatioTasksUsingGet: "/synthesis/ratio-task", // 获取配比任务列表
+ queryRatioTaskByIdUsingGet: "/synthesis/ratio-task/:taskId", // 根据ID获取配比任务详情
+ deleteRatioTaskByIdUsingDelete: "/synthesis/ratio-task/:taskId", // 删除配比任务
+ updateRatioTaskByIdUsingPut: "/synthesis/ratio-task/:taskId", // 更新配比任务
+ executeRatioTaskByIdUsingPost: "/synthesis/ratio-task/:taskId/execute", // 执行配比任务
+ stopRatioTaskByIdUsingPost: "/synthesis/ratio-task/:taskId/stop", // 停止配比任务
+ queryRatioJobStatusUsingGet: "/synthesis/ratio-task/:taskId/status", // 获取配比任务状态
+ queryRatioModelsUsingGet: "/synthesis/ratio-models", // 获取配比模型列表
+
+ // 数据评测接口
+ queryEvaluationTasksUsingPost: "/evaluation/tasks", // 获取评测任务列表
+ createEvaluationTaskUsingPost: "/evaluation/tasks/create", // 创建评测任务
+ queryEvaluationTaskByIdUsingGet: "/evaluation/tasks/:taskId", // 根据ID获取评测任务详情
+ updateEvaluationTaskByIdUsingPut: "/evaluation/tasks/:taskId", // 更新评测任务
+ deleteEvaluationTaskByIdUsingDelete: "/evaluation/tasks/:taskId", // 删除评测任务
+ executeEvaluationTaskByIdUsingPost: "/evaluation/tasks/:taskId/execute", // 执行评测任务
+ stopEvaluationTaskByIdUsingPost: "/evaluation/tasks/:taskId/stop", // 停止评测任务
+ queryEvaluationReportsUsingPost: "/evaluation/reports", // 获取评测报告列表
+ queryEvaluationReportByIdUsingGet: "/evaluation/reports/:reportId", // 根据ID获取评测报告详情
+ manualEvaluateUsingPost: "/evaluation/manual-evaluate", // 人工评测
+ queryEvaluationStatisticsUsingGet: "/evaluation/statistics", // 获取评测统计信息
+ evaluateDataQualityUsingPost: "/evaluation/data-quality", // 数据质量评测
+ getQualityEvaluationByIdUsingGet: "/evaluation/data-quality/:id", // 根据ID获取数据质量评测详情
+ evaluateCompatibilityUsingPost: "/evaluation/compatibility", // 兼容性评测
+ evaluateValueUsingPost: "/evaluation/value", // 价值评测
+ queryEvaluationReportsUsingGet: "/evaluation/reports", // 获取评测报告列表(简化版)
+ getEvaluationReportByIdUsingGet: "/evaluation/reports/:reportId", // 根据ID获取评测报告详情(简化版)
+ exportEvaluationReportUsingGet: "/evaluation/reports/:reportId/export", // 导出评测报告
+ batchEvaluationUsingPost: "/evaluation/batch-evaluate", // 批量评测
+
+ // 知识生成接口
+ queryKnowledgeBasesUsingPost: "/knowledge-base/list", // 获取知识库列表
+ createKnowledgeBaseUsingPost: "/knowledge-base/create", // 创建知识库
+ queryKnowledgeBaseByIdUsingGet: "/knowledge-base/:baseId", // 根据ID获取知识库详情
+ updateKnowledgeBaseByIdUsingPut: "/knowledge-base/:baseId", // 更新知识库
+ deleteKnowledgeBaseByIdUsingDelete: "/knowledge-base/:baseId", // 删除知识库
+ addKnowledgeBaseFilesUsingPost: "/knowledge-base/:baseId/files", // 添加文件到知识库
+ queryKnowledgeBaseFilesGet: "/knowledge-base/:baseId/files", // 根据ID获取知识生成文件列表
+ queryKnowledgeBaseFilesByIdUsingGet:
+ "/knowledge-base/:baseId/files/:fileId", // 根据ID获取知识生成文件详情
+ deleteKnowledgeBaseTaskByIdUsingDelete: "/knowledge-base/:baseId/files/:id", // 删除知识生成文件
+
+ // 算子市场
+ queryOperatorsUsingPost: "/operators/list", // 获取算子列表
+ queryCategoryTreeUsingGet: "/categories/tree", // 获取算子分类树
+ queryOperatorByIdUsingGet: "/operators/:id", // 根据ID获取算子详情
+ createOperatorUsingPost: "/operators/create", // 创建算子
+ updateOperatorByIdUsingPut: "/operators/:id", // 更新算子
+ uploadOperatorUsingPost: "/operators/upload", // 上传算子
+ uploadFileChunkUsingPost: "/operators/upload/chunk", // 上传切片
+ preUploadOperatorUsingPost: "/operators/upload/pre-upload", // 预上传文件
+ cancelUploadOperatorUsingPut: "/operators/upload/cancel-upload", // 取消上传
+
+ createLabelUsingPost: "/operators/labels", // 创建算子标签
+ queryLabelsUsingGet: "/labels", // 获取算子标签列表
+ deleteLabelsUsingDelete: "/labels", // 删除算子标签
+ updateLabelByIdUsingPut: "/labels/:labelId", // 更新算子标签
+ deleteOperatorByIdUsingDelete: "/operators/:operatorId", // 删除算子
+ publishOperatorUsingPost: "/operators/:operatorId/publish", // 发布算子
+ unpublishOperatorUsingPost: "/operators/:operatorId/unpublish", // 下架算子
+
+ // 设置接口
+ queryModelsUsingGet: "/models/list", // 获取模型列表
+ queryProvidersUsingGet: "/models/providers", // 获取模型提供商列表
+ createModelUsingPost: "/models/create", // 创建模型
+ updateModelUsingPut: "/models/:id", // 更新模型
+ deleteModelUsingDelete: "/models/:id", // 删除模型
+};
+
+module.exports = addMockPrefix("/api", MockAPI);
diff --git a/frontend/src/mock/mock-core/module-loader.cjs b/frontend/src/mock/mock-core/module-loader.cjs
index 32cc778f0..4084fc1ed 100644
--- a/frontend/src/mock/mock-core/module-loader.cjs
+++ b/frontend/src/mock/mock-core/module-loader.cjs
@@ -1,25 +1,25 @@
-const fs = require('fs');
-
-function loadAllMockModules(router, pathDir) {
- if (!fs.existsSync(pathDir)) {
- throw new Error(`Mock directory ${pathDir} does not exist.`);
- }
-
- const files = fs.readdirSync(pathDir);
- files.forEach(file => {
- const filePath = `${pathDir}/${file}`;
- if(fs.lstatSync(filePath).isDirectory()) {
- loadAllMockModules(router, filePath);
- } else {
- let fileNameModule = file.replace('/\.js\b$/', '');
- let module = require(`${pathDir}/${fileNameModule}`);
- if(typeof module === 'function' && module.length === 1) {
- module(router);
- }
- }
- });
-}
-
-module.exports = {
- loadAllMockModules,
+const fs = require('fs');
+
+function loadAllMockModules(router, pathDir) {
+ if (!fs.existsSync(pathDir)) {
+ throw new Error(`Mock directory ${pathDir} does not exist.`);
+ }
+
+ const files = fs.readdirSync(pathDir);
+ files.forEach(file => {
+ const filePath = `${pathDir}/${file}`;
+ if(fs.lstatSync(filePath).isDirectory()) {
+ loadAllMockModules(router, filePath);
+ } else {
+ let fileNameModule = file.replace('/\.js\b$/', '');
+ let module = require(`${pathDir}/${fileNameModule}`);
+ if(typeof module === 'function' && module.length === 1) {
+ module(router);
+ }
+ }
+ });
+}
+
+module.exports = {
+ loadAllMockModules,
};
\ No newline at end of file
diff --git a/frontend/src/mock/mock-core/session-helper.cjs b/frontend/src/mock/mock-core/session-helper.cjs
index b01ea237b..d92f382c9 100644
--- a/frontend/src/mock/mock-core/session-helper.cjs
+++ b/frontend/src/mock/mock-core/session-helper.cjs
@@ -1,63 +1,63 @@
-const path = require("path");
-const Mock = require("mockjs");
-const session = require("express-session");
-const FileStore = require("session-file-store")(session);
-
-const { isFunction } = require("lodash");
-
-const argv = require("minimist")(process.argv.slice(2));
-const isDev = (argv.env || "development") === "development";
-const TOKEN_KEY = isDev ? "X-Auth-Token" : "X-Csrf-Token";
-
-const setSessionUser = (req, getLoginInfo) => {
- if (!isFunction(getLoginInfo)) {
- throw new Error("getLoginInfo must be a function");
- }
-
- if (!req.session?.users) {
- req.session.users = {};
- }
-
- let token = req.get(TOKEN_KEY);
- const { users } = req.session;
- if (!token || !users[token]) {
- token = Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, "");
- const userInfo = getLoginInfo(req) || {};
- users[token] = user;
- }
- return token;
-};
-
-const getSessionUser = (req) => {
- const token = req.get(TOKEN_KEY);
- if (token && req.session?.users) {
- return req.session.users[token];
- }
- return null;
-};
-
-const genExpressSession = () => {
- return session({
- name: "demo.name",
- secret: "demo.secret",
- resave: true,
- saveUninitialized: true,
- cookie: {
- maxAge: 60 * 60 * 1e3,
- expires: new Date(Date.now() + 60 * 60 * 1e3),
- }, // 1 hour
- store: new FileStore({
- path: path.join(__dirname, "../sessions"),
- retries: 0,
- keyFunction: (secret, sessionId) => {
- return secret + sessionId;
- },
- }),
- });
-};
-
-module.exports = {
- setSessionUser,
- getSessionUser,
- genExpressSession,
-};
+const path = require("path");
+const Mock = require("mockjs");
+const session = require("express-session");
+const FileStore = require("session-file-store")(session);
+
+const { isFunction } = require("lodash");
+
+const argv = require("minimist")(process.argv.slice(2));
+const isDev = (argv.env || "development") === "development";
+const TOKEN_KEY = isDev ? "X-Auth-Token" : "X-Csrf-Token";
+
+const setSessionUser = (req, getLoginInfo) => {
+ if (!isFunction(getLoginInfo)) {
+ throw new Error("getLoginInfo must be a function");
+ }
+
+ if (!req.session?.users) {
+ req.session.users = {};
+ }
+
+ let token = req.get(TOKEN_KEY);
+ const { users } = req.session;
+ if (!token || !users[token]) {
+ token = Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, "");
+ const userInfo = getLoginInfo(req) || {};
+ users[token] = user;
+ }
+ return token;
+};
+
+const getSessionUser = (req) => {
+ const token = req.get(TOKEN_KEY);
+ if (token && req.session?.users) {
+ return req.session.users[token];
+ }
+ return null;
+};
+
+const genExpressSession = () => {
+ return session({
+ name: "demo.name",
+ secret: "demo.secret",
+ resave: true,
+ saveUninitialized: true,
+ cookie: {
+ maxAge: 60 * 60 * 1e3,
+ expires: new Date(Date.now() + 60 * 60 * 1e3),
+ }, // 1 hour
+ store: new FileStore({
+ path: path.join(__dirname, "../sessions"),
+ retries: 0,
+ keyFunction: (secret, sessionId) => {
+ return secret + sessionId;
+ },
+ }),
+ });
+};
+
+module.exports = {
+ setSessionUser,
+ getSessionUser,
+ genExpressSession,
+};
diff --git a/frontend/src/mock/mock-core/util.cjs b/frontend/src/mock/mock-core/util.cjs
index 327f7c224..ef4a065f9 100644
--- a/frontend/src/mock/mock-core/util.cjs
+++ b/frontend/src/mock/mock-core/util.cjs
@@ -1,30 +1,30 @@
-
-function log(message, type = "log", provided = 'console') {
- const providedFn = globalThis[provided] || console;
- if (providedFn && typeof providedFn[type] === 'function') {
- const invokeMethod = providedFn[type ?? 'log'];
- invokeMethod.call(providedFn, message);
- }
-}
-
-function addMockPrefix(urlPrefix, api) {
- const newMockApi = {};
- Object.keys(api).map(apiKey=>{
- newMockApi[apiKey] = urlPrefix + api[apiKey];
- });
-
- return new Proxy(newMockApi, {
- get(target, prop) {
- if (prop in target) {
- return target[prop];
- } else {
- throw new Error(`API ${String(prop)} is not defined.`);
- }
- }
- })
-}
-
-module.exports = {
- log,
- addMockPrefix,
+
+function log(message, type = "log", provided = 'console') {
+ const providedFn = globalThis[provided] || console;
+ if (providedFn && typeof providedFn[type] === 'function') {
+ const invokeMethod = providedFn[type ?? 'log'];
+ invokeMethod.call(providedFn, message);
+ }
+}
+
+function addMockPrefix(urlPrefix, api) {
+ const newMockApi = {};
+ Object.keys(api).map(apiKey=>{
+ newMockApi[apiKey] = urlPrefix + api[apiKey];
+ });
+
+ return new Proxy(newMockApi, {
+ get(target, prop) {
+ if (prop in target) {
+ return target[prop];
+ } else {
+ throw new Error(`API ${String(prop)} is not defined.`);
+ }
+ }
+ })
+}
+
+module.exports = {
+ log,
+ addMockPrefix,
};
\ No newline at end of file
diff --git a/frontend/src/mock/mock-middleware/error-handle-middleware.cjs b/frontend/src/mock/mock-middleware/error-handle-middleware.cjs
index 272c0ae88..d1e50c89f 100644
--- a/frontend/src/mock/mock-middleware/error-handle-middleware.cjs
+++ b/frontend/src/mock/mock-middleware/error-handle-middleware.cjs
@@ -1,13 +1,13 @@
-const errorHandle = (err, req, res, next) => {
- if(res.headersSent) {
- return next(err);
- }
- console.error('Server Error:', err.message);
- res.status(500).json({
- code: '500',
- msg: 'Internal Server Error',
- data: null,
- });
-};
-
-module.exports = errorHandle;
+const errorHandle = (err, req, res, next) => {
+ if(res.headersSent) {
+ return next(err);
+ }
+ console.error('Server Error:', err.message);
+ res.status(500).json({
+ code: '500',
+ msg: 'Internal Server Error',
+ data: null,
+ });
+};
+
+module.exports = errorHandle;
diff --git a/frontend/src/mock/mock-middleware/index.cjs b/frontend/src/mock/mock-middleware/index.cjs
index e9f2ff0f0..01c9e567a 100644
--- a/frontend/src/mock/mock-middleware/index.cjs
+++ b/frontend/src/mock/mock-middleware/index.cjs
@@ -1,11 +1,11 @@
-const setHeader = require('./set-header-middleware.cjs');
-const strongMatch = require('./strong-match-middleware.cjs');
-const sendJSON = require('./send-json-middleawre.cjs');
-const errorHandle = require('./error-handle-middleware.cjs');
-
-module.exports = {
- setHeader,
- strongMatch,
- sendJSON,
- errorHandle,
+const setHeader = require('./set-header-middleware.cjs');
+const strongMatch = require('./strong-match-middleware.cjs');
+const sendJSON = require('./send-json-middleawre.cjs');
+const errorHandle = require('./error-handle-middleware.cjs');
+
+module.exports = {
+ setHeader,
+ strongMatch,
+ sendJSON,
+ errorHandle,
};
\ No newline at end of file
diff --git a/frontend/src/mock/mock-middleware/send-json-middleawre.cjs b/frontend/src/mock/mock-middleware/send-json-middleawre.cjs
index a7a493f6f..b6a1a47b4 100644
--- a/frontend/src/mock/mock-middleware/send-json-middleawre.cjs
+++ b/frontend/src/mock/mock-middleware/send-json-middleawre.cjs
@@ -1,18 +1,18 @@
-const sendJSON = (req, res, next) => {
- res.sendJSON = (
- data = null,
- { code = '0', msg = 'success', statusCode = 200, timeout = 0 } = {}
- ) => {
- const timer = setTimeout(() => {
- res.status(statusCode).json({
- code,
- msg,
- data,
- });
- clearTimeout(timer);
- }, timeout);
- };
- next();
-};
-
+const sendJSON = (req, res, next) => {
+ res.sendJSON = (
+ data = null,
+ { code = '0', msg = 'success', statusCode = 200, timeout = 0 } = {}
+ ) => {
+ const timer = setTimeout(() => {
+ res.status(statusCode).json({
+ code,
+ msg,
+ data,
+ });
+ clearTimeout(timer);
+ }, timeout);
+ };
+ next();
+};
+
module.exports = sendJSON;
\ No newline at end of file
diff --git a/frontend/src/mock/mock-middleware/set-header-middleware.cjs b/frontend/src/mock/mock-middleware/set-header-middleware.cjs
index 49694e470..821c6c3bb 100644
--- a/frontend/src/mock/mock-middleware/set-header-middleware.cjs
+++ b/frontend/src/mock/mock-middleware/set-header-middleware.cjs
@@ -1,14 +1,14 @@
-const setHeader = (req, res, next) => {
- res.set({
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
- 'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src *; font-src 'self';",
- 'X-Content-Type-Options': 'nosniff',
- 'X-Frame-Options': 'SAMEORIGIN',
- 'X-XSS-Protection': '1; mode=block',
- });
- next();
-};
-
+const setHeader = (req, res, next) => {
+ res.set({
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
+ 'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src *; font-src 'self';",
+ 'X-Content-Type-Options': 'nosniff',
+ 'X-Frame-Options': 'SAMEORIGIN',
+ 'X-XSS-Protection': '1; mode=block',
+ });
+ next();
+};
+
module.exports = setHeader;
\ No newline at end of file
diff --git a/frontend/src/mock/mock-middleware/strong-match-middleware.cjs b/frontend/src/mock/mock-middleware/strong-match-middleware.cjs
index 8d0a1be8e..a11813e3f 100644
--- a/frontend/src/mock/mock-middleware/strong-match-middleware.cjs
+++ b/frontend/src/mock/mock-middleware/strong-match-middleware.cjs
@@ -1,13 +1,13 @@
-const API = require('../mock-apis.cjs');
-
-const strongMatch = (req, res, next) => {
- res.strongMatch = () => {
- const { url } = req;
- const index = url.indexOf('?');
- const targetUrl = index !== -1 ? url.substring(0, index) : url;
- const isExistedUrl = Object.values(API).includes(targetUrl);
- return isExistedUrl;
- };
- next();
-};
+const API = require('../mock-apis.cjs');
+
+const strongMatch = (req, res, next) => {
+ res.strongMatch = () => {
+ const { url } = req;
+ const index = url.indexOf('?');
+ const targetUrl = index !== -1 ? url.substring(0, index) : url;
+ const isExistedUrl = Object.values(API).includes(targetUrl);
+ return isExistedUrl;
+ };
+ next();
+};
module.exports = strongMatch;
\ No newline at end of file
diff --git a/frontend/src/mock/mock-seed/data-annotation.cjs b/frontend/src/mock/mock-seed/data-annotation.cjs
index be8fe88a8..6ba179916 100644
--- a/frontend/src/mock/mock-seed/data-annotation.cjs
+++ b/frontend/src/mock/mock-seed/data-annotation.cjs
@@ -1,618 +1,618 @@
-const Mock = require("mockjs");
-const API = require("../mock-apis.cjs");
-
-// 标注任务数据
-function annotationTaskItem() {
- return {
- source_dataset_id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- mapping_id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- labelling_project_id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- labelling_project_name: Mock.Random.ctitle(5, 20),
- created_at: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- last_updated_at: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- deleted_at: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- // id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- // name: Mock.Random.ctitle(5, 20),
- // description: Mock.Random.csentence(5, 30),
- // type: Mock.Random.pick([
- // "TEXT_CLASSIFICATION",
- // "NAMED_ENTITY_RECOGNITION",
- // "OBJECT_DETECTION",
- // "SEMANTIC_SEGMENTATION",
- // ]),
- // status: Mock.Random.pick(["PENDING", "IN_PROGRESS", "COMPLETED", "PAUSED"]),
- // datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- // progress: Mock.Random.float(0, 100, 2, 2),
- // createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- // updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- // createdBy: Mock.Random.cname(),
- // assignedTo: Mock.Random.cname(),
- // totalDataCount: Mock.Random.integer(100, 10000),
- // annotatedCount: Mock.Random.integer(10, 500),
- // configuration: {
- // labels: Mock.Random.shuffle([
- // "正面",
- // "负面",
- // "中性",
- // "人物",
- // "地点",
- // "组织",
- // "时间",
- // ]).slice(0, Mock.Random.integer(3, 5)),
- // guidelines: Mock.Random.csentence(10, 50),
- // qualityThreshold: Mock.Random.float(0.8, 1.0, 2, 2),
- // },
- // statistics: {
- // accuracy: Mock.Random.float(0.85, 0.99, 2, 2),
- // averageTime: Mock.Random.integer(30, 300), // seconds
- // reviewCount: Mock.Random.integer(0, 50),
- // },
- };
-}
-
-const annotationTaskList = new Array(25).fill(null).map(annotationTaskItem);
-
-// 标注数据项
-function annotationDataItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- taskId: Mock.Random.pick(annotationTaskList).id,
- content: Mock.Random.cparagraph(1, 3),
- originalData: {
- text: Mock.Random.cparagraph(1, 3),
- source: Mock.Random.url(),
- metadata: {
- author: Mock.Random.cname(),
- timestamp: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- },
- },
- annotations: [
- {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- label: Mock.Random.pick(["正面", "负面", "中性"]),
- confidence: Mock.Random.float(0.7, 1.0, 2, 2),
- annotator: Mock.Random.cname(),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- isPreAnnotation: Mock.Random.boolean(),
- },
- ],
- status: Mock.Random.pick(["PENDING", "ANNOTATED", "REVIEWED", "REJECTED"]),
- priority: Mock.Random.integer(1, 5),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- };
-}
-
-const annotationDataList = new Array(200).fill(null).map(annotationDataItem);
-
-// 标注模板数据
-function annotationTemplateItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.ctitle(5, 15),
- description: Mock.Random.csentence(5, 25),
- type: Mock.Random.pick([
- "TEXT_CLASSIFICATION",
- "NAMED_ENTITY_RECOGNITION",
- "OBJECT_DETECTION",
- "SEMANTIC_SEGMENTATION",
- ]),
- category: Mock.Random.ctitle(3, 8),
- labels: Mock.Random.shuffle([
- "正面",
- "负面",
- "中性",
- "人物",
- "地点",
- "组织",
- "时间",
- "产品",
- "服务",
- ]).slice(0, Mock.Random.integer(3, 6)),
- guidelines: Mock.Random.csentence(10, 50),
- usageCount: Mock.Random.integer(0, 100),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- createdBy: Mock.Random.cname(),
- };
-}
-
-const annotationTemplateList = new Array(15)
- .fill(null)
- .map(annotationTemplateItem);
-
-// 标注者数据
-function annotatorItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.cname(),
- email: Mock.Random.email(),
- role: Mock.Random.pick(["ANNOTATOR", "REVIEWER", "ADMIN"]),
- skillLevel: Mock.Random.pick(["BEGINNER", "INTERMEDIATE", "EXPERT"]),
- specialties: Mock.Random.shuffle([
- "文本分类",
- "命名实体识别",
- "目标检测",
- "语义分割",
- ]).slice(0, Mock.Random.integer(1, 3)),
- statistics: {
- totalAnnotations: Mock.Random.integer(100, 5000),
- accuracy: Mock.Random.float(0.85, 0.99, 2, 2),
- averageSpeed: Mock.Random.integer(50, 200), // annotations per hour
- totalWorkTime: Mock.Random.integer(10, 500), // hours
- },
- status: Mock.Random.pick(["ACTIVE", "INACTIVE", "SUSPENDED"]),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- };
-}
-
-const annotatorList = new Array(20).fill(null).map(annotatorItem);
-
-module.exports = function (router) {
- // 获取标注任务列表
- router.get(API.queryAnnotationTasksUsingGet, (req, res) => {
- const { page = 0, size = 20, status, type } = req.query;
- let filteredTasks = annotationTaskList;
-
- if (status) {
- filteredTasks = filteredTasks.filter((task) => task.status === status);
- }
-
- if (type) {
- filteredTasks = filteredTasks.filter((task) => task.type === type);
- }
-
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = filteredTasks.slice(startIndex, endIndex);
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- content: pageData,
- totalElements: filteredTasks.length,
- totalPages: Math.ceil(filteredTasks.length / size),
- size: parseInt(size),
- number: parseInt(page),
- first: page == 0,
- last: page >= Math.ceil(filteredTasks.length / size) - 1,
- },
- });
- });
-
- // 创建标注任务
- router.post(API.createAnnotationTaskUsingPost, (req, res) => {
- const newTask = {
- ...annotationTaskItem(),
- ...req.body,
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- status: "PENDING",
- progress: 0,
- annotatedCount: 0,
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString(),
- };
- annotationTaskList.push(newTask);
-
- res.status(201).send({
- code: "0",
- msg: "Annotation task created successfully",
- data: newTask,
- });
- });
-
- // 获取标注任务详情
- router.get(API.queryAnnotationTaskByIdUsingGet, (req, res) => {
- const { taskId } = req.params;
- const task = annotationTaskList.find((t) => t.id === taskId);
-
- if (task) {
- res.send({
- code: "0",
- msg: "Success",
- data: task,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Annotation task not found",
- data: null,
- });
- }
- });
-
- // 更新标注任务
- router.put(API.syncAnnotationTaskByIdUsingPost, (req, res) => {
- const { taskId } = req.params;
- const index = annotationTaskList.findIndex((t) => t.id === taskId);
-
- if (index !== -1) {
- annotationTaskList[index] = {
- ...annotationTaskList[index],
- ...req.body,
- updatedAt: new Date().toISOString(),
- };
- res.send({
- code: "0",
- msg: "Annotation task updated successfully",
- data: annotationTaskList[index],
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Annotation task not found",
- data: null,
- });
- }
- });
-
- // 删除标注任务
- router.delete(API.deleteAnnotationTaskByIdUsingDelete, (req, res) => {
- const { taskId } = req.params;
- const index = annotationTaskList.findIndex((t) => t.id === taskId);
-
- if (index !== -1) {
- annotationTaskList.splice(index, 1);
- res.send({
- code: "0",
- msg: "Annotation task deleted successfully",
- data: null,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Annotation task not found",
- data: null,
- });
- }
- });
-
- // 获取标注数据列表
- router.get(API.queryAnnotationDataUsingGet, (req, res) => {
- const { taskId } = req.params;
- const { page = 0, size = 20, status } = req.query;
-
- let filteredData = annotationDataList.filter(
- (data) => data.taskId === taskId
- );
-
- if (status) {
- filteredData = filteredData.filter((data) => data.status === status);
- }
-
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = filteredData.slice(startIndex, endIndex);
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- content: pageData,
- totalElements: filteredData.length,
- totalPages: Math.ceil(filteredData.length / size),
- size: parseInt(size),
- number: parseInt(page),
- },
- });
- });
-
- // 提交标注
- router.post(API.submitAnnotationUsingPost, (req, res) => {
- const { taskId } = req.params;
- const newAnnotation = {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- taskId,
- ...req.body,
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString(),
- };
-
- res.status(201).send({
- code: "0",
- msg: "Annotation submitted successfully",
- data: newAnnotation,
- });
- });
-
- // 更新标注
- router.put(API.updateAnnotationUsingPut, (req, res) => {
- const { taskId, annotationId } = req.params;
-
- res.send({
- code: "0",
- msg: "Annotation updated successfully",
- data: {
- id: annotationId,
- taskId,
- ...req.body,
- updatedAt: new Date().toISOString(),
- },
- });
- });
-
- // 删除标注
- router.delete(API.deleteAnnotationUsingDelete, (req, res) => {
- const { taskId, annotationId } = req.params;
-
- res.send({
- code: "0",
- msg: "Annotation deleted successfully",
- data: null,
- });
- });
-
- // 开始标注任务
- router.post(API.startAnnotationTaskUsingPost, (req, res) => {
- const { taskId } = req.params;
- const task = annotationTaskList.find((t) => t.id === taskId);
-
- if (task) {
- task.status = "IN_PROGRESS";
- task.updatedAt = new Date().toISOString();
-
- res.send({
- code: "0",
- msg: "Annotation task started successfully",
- data: task,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Annotation task not found",
- data: null,
- });
- }
- });
-
- // 暂停标注任务
- router.post(API.pauseAnnotationTaskUsingPost, (req, res) => {
- const { taskId } = req.params;
- const task = annotationTaskList.find((t) => t.id === taskId);
-
- if (task) {
- task.status = "PAUSED";
- task.updatedAt = new Date().toISOString();
-
- res.send({
- code: "0",
- msg: "Annotation task paused successfully",
- data: task,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Annotation task not found",
- data: null,
- });
- }
- });
-
- // 恢复标注任务
- router.post(API.resumeAnnotationTaskUsingPost, (req, res) => {
- const { taskId } = req.params;
- const task = annotationTaskList.find((t) => t.id === taskId);
-
- if (task) {
- task.status = "IN_PROGRESS";
- task.updatedAt = new Date().toISOString();
-
- res.send({
- code: "0",
- msg: "Annotation task resumed successfully",
- data: task,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Annotation task not found",
- data: null,
- });
- }
- });
-
- // 完成标注任务
- router.post(API.completeAnnotationTaskUsingPost, (req, res) => {
- const { taskId } = req.params;
- const task = annotationTaskList.find((t) => t.id === taskId);
-
- if (task) {
- task.status = "COMPLETED";
- task.progress = 100;
- task.updatedAt = new Date().toISOString();
-
- res.send({
- code: "0",
- msg: "Annotation task completed successfully",
- data: task,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Annotation task not found",
- data: null,
- });
- }
- });
-
- // 获取标注任务统计信息
- router.get(API.getAnnotationTaskStatisticsUsingGet, (req, res) => {
- const { taskId } = req.params;
- const task = annotationTaskList.find((t) => t.id === taskId);
-
- if (task) {
- const statistics = {
- taskId,
- totalDataCount: task.totalDataCount,
- annotatedCount: task.annotatedCount,
- progress: task.progress,
- accuracy: task.statistics.accuracy,
- averageAnnotationTime: task.statistics.averageTime,
- reviewCount: task.statistics.reviewCount,
- qualityScore: Mock.Random.float(0.8, 0.99, 2, 2),
- annotatorDistribution: {
- [Mock.Random.cname()]: Mock.Random.integer(10, 100),
- [Mock.Random.cname()]: Mock.Random.integer(10, 100),
- [Mock.Random.cname()]: Mock.Random.integer(10, 100),
- },
- };
-
- res.send({
- code: "0",
- msg: "Success",
- data: statistics,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Annotation task not found",
- data: null,
- });
- }
- });
-
- // 获取整体标注统计信息
- router.get(API.getAnnotationStatisticsUsingGet, (req, res) => {
- const statistics = {
- totalTasks: annotationTaskList.length,
- completedTasks: annotationTaskList.filter((t) => t.status === "COMPLETED")
- .length,
- inProgressTasks: annotationTaskList.filter(
- (t) => t.status === "IN_PROGRESS"
- ).length,
- pendingTasks: annotationTaskList.filter((t) => t.status === "PENDING")
- .length,
- totalAnnotations: annotationDataList.length,
- totalAnnotators: annotatorList.length,
- averageAccuracy: Mock.Random.float(0.85, 0.95, 2, 2),
- taskTypeDistribution: {
- TEXT_CLASSIFICATION: Mock.Random.integer(5, 15),
- NAMED_ENTITY_RECOGNITION: Mock.Random.integer(3, 10),
- OBJECT_DETECTION: Mock.Random.integer(2, 8),
- SEMANTIC_SEGMENTATION: Mock.Random.integer(1, 5),
- },
- };
-
- res.send({
- code: "0",
- msg: "Success",
- data: statistics,
- });
- });
-
- // 获取标注模板列表
- router.get(API.queryAnnotationTemplatesUsingGet, (req, res) => {
- const { page = 0, size = 20, type } = req.query;
- let filteredTemplates = annotationTemplateList;
-
- if (type) {
- filteredTemplates = filteredTemplates.filter(
- (template) => template.type === type
- );
- }
-
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = filteredTemplates.slice(startIndex, endIndex);
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- content: pageData,
- totalElements: filteredTemplates.length,
- totalPages: Math.ceil(filteredTemplates.length / size),
- size: parseInt(size),
- number: parseInt(page),
- },
- });
- });
-
- // 创建标注模板
- router.post(API.createAnnotationTemplateUsingPost, (req, res) => {
- const newTemplate = {
- ...annotationTemplateItem(),
- ...req.body,
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- usageCount: 0,
- createdAt: new Date().toISOString(),
- };
- annotationTemplateList.push(newTemplate);
-
- res.status(201).send({
- code: "0",
- msg: "Annotation template created successfully",
- data: newTemplate,
- });
- });
-
- // 获取标注模板详情
- router.get(API.queryAnnotationTemplateByIdUsingGet, (req, res) => {
- const { templateId } = req.params;
- const template = annotationTemplateList.find((t) => t.id === templateId);
-
- if (template) {
- res.send({
- code: "0",
- msg: "Success",
- data: template,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Annotation template not found",
- data: null,
- });
- }
- });
-
- // 获取标注者列表
- router.get(API.queryAnnotatorsUsingGet, (req, res) => {
- const { page = 0, size = 20, status, skillLevel } = req.query;
- let filteredAnnotators = annotatorList;
-
- if (status) {
- filteredAnnotators = filteredAnnotators.filter(
- (annotator) => annotator.status === status
- );
- }
-
- if (skillLevel) {
- filteredAnnotators = filteredAnnotators.filter(
- (annotator) => annotator.skillLevel === skillLevel
- );
- }
-
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = filteredAnnotators.slice(startIndex, endIndex);
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- content: pageData,
- totalElements: filteredAnnotators.length,
- totalPages: Math.ceil(filteredAnnotators.length / size),
- size: parseInt(size),
- number: parseInt(page),
- },
- });
- });
-
- // 分配标注者
- router.post(API.assignAnnotatorUsingPost, (req, res) => {
- const { taskId } = req.params;
- const { annotatorIds } = req.body;
-
- res.send({
- code: "0",
- msg: "Annotators assigned successfully",
- data: {
- taskId,
- assignedAnnotators: annotatorIds,
- assignedAt: new Date().toISOString(),
- },
- });
- });
-};
+const Mock = require("mockjs");
+const API = require("../mock-apis.cjs");
+
+// 标注任务数据
+function annotationTaskItem() {
+ return {
+ source_dataset_id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ mapping_id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ labelling_project_id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ labelling_project_name: Mock.Random.ctitle(5, 20),
+ created_at: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ last_updated_at: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ deleted_at: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ // id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ // name: Mock.Random.ctitle(5, 20),
+ // description: Mock.Random.csentence(5, 30),
+ // type: Mock.Random.pick([
+ // "TEXT_CLASSIFICATION",
+ // "NAMED_ENTITY_RECOGNITION",
+ // "OBJECT_DETECTION",
+ // "SEMANTIC_SEGMENTATION",
+ // ]),
+ // status: Mock.Random.pick(["PENDING", "IN_PROGRESS", "COMPLETED", "PAUSED"]),
+ // datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ // progress: Mock.Random.float(0, 100, 2, 2),
+ // createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ // updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ // createdBy: Mock.Random.cname(),
+ // assignedTo: Mock.Random.cname(),
+ // totalDataCount: Mock.Random.integer(100, 10000),
+ // annotatedCount: Mock.Random.integer(10, 500),
+ // configuration: {
+ // labels: Mock.Random.shuffle([
+ // "正面",
+ // "负面",
+ // "中性",
+ // "人物",
+ // "地点",
+ // "组织",
+ // "时间",
+ // ]).slice(0, Mock.Random.integer(3, 5)),
+ // guidelines: Mock.Random.csentence(10, 50),
+ // qualityThreshold: Mock.Random.float(0.8, 1.0, 2, 2),
+ // },
+ // statistics: {
+ // accuracy: Mock.Random.float(0.85, 0.99, 2, 2),
+ // averageTime: Mock.Random.integer(30, 300), // seconds
+ // reviewCount: Mock.Random.integer(0, 50),
+ // },
+ };
+}
+
+const annotationTaskList = new Array(25).fill(null).map(annotationTaskItem);
+
+// 标注数据项
+function annotationDataItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ taskId: Mock.Random.pick(annotationTaskList).id,
+ content: Mock.Random.cparagraph(1, 3),
+ originalData: {
+ text: Mock.Random.cparagraph(1, 3),
+ source: Mock.Random.url(),
+ metadata: {
+ author: Mock.Random.cname(),
+ timestamp: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ },
+ },
+ annotations: [
+ {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ label: Mock.Random.pick(["正面", "负面", "中性"]),
+ confidence: Mock.Random.float(0.7, 1.0, 2, 2),
+ annotator: Mock.Random.cname(),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ isPreAnnotation: Mock.Random.boolean(),
+ },
+ ],
+ status: Mock.Random.pick(["PENDING", "ANNOTATED", "REVIEWED", "REJECTED"]),
+ priority: Mock.Random.integer(1, 5),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ };
+}
+
+const annotationDataList = new Array(200).fill(null).map(annotationDataItem);
+
+// 标注模板数据
+function annotationTemplateItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.ctitle(5, 15),
+ description: Mock.Random.csentence(5, 25),
+ type: Mock.Random.pick([
+ "TEXT_CLASSIFICATION",
+ "NAMED_ENTITY_RECOGNITION",
+ "OBJECT_DETECTION",
+ "SEMANTIC_SEGMENTATION",
+ ]),
+ category: Mock.Random.ctitle(3, 8),
+ labels: Mock.Random.shuffle([
+ "正面",
+ "负面",
+ "中性",
+ "人物",
+ "地点",
+ "组织",
+ "时间",
+ "产品",
+ "服务",
+ ]).slice(0, Mock.Random.integer(3, 6)),
+ guidelines: Mock.Random.csentence(10, 50),
+ usageCount: Mock.Random.integer(0, 100),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ createdBy: Mock.Random.cname(),
+ };
+}
+
+const annotationTemplateList = new Array(15)
+ .fill(null)
+ .map(annotationTemplateItem);
+
+// 标注者数据
+function annotatorItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.cname(),
+ email: Mock.Random.email(),
+ role: Mock.Random.pick(["ANNOTATOR", "REVIEWER", "ADMIN"]),
+ skillLevel: Mock.Random.pick(["BEGINNER", "INTERMEDIATE", "EXPERT"]),
+ specialties: Mock.Random.shuffle([
+ "文本分类",
+ "命名实体识别",
+ "目标检测",
+ "语义分割",
+ ]).slice(0, Mock.Random.integer(1, 3)),
+ statistics: {
+ totalAnnotations: Mock.Random.integer(100, 5000),
+ accuracy: Mock.Random.float(0.85, 0.99, 2, 2),
+ averageSpeed: Mock.Random.integer(50, 200), // annotations per hour
+ totalWorkTime: Mock.Random.integer(10, 500), // hours
+ },
+ status: Mock.Random.pick(["ACTIVE", "INACTIVE", "SUSPENDED"]),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ };
+}
+
+const annotatorList = new Array(20).fill(null).map(annotatorItem);
+
+module.exports = function (router) {
+ // 获取标注任务列表
+ router.get(API.queryAnnotationTasksUsingGet, (req, res) => {
+ const { page = 0, size = 20, status, type } = req.query;
+ let filteredTasks = annotationTaskList;
+
+ if (status) {
+ filteredTasks = filteredTasks.filter((task) => task.status === status);
+ }
+
+ if (type) {
+ filteredTasks = filteredTasks.filter((task) => task.type === type);
+ }
+
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = filteredTasks.slice(startIndex, endIndex);
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: pageData,
+ totalElements: filteredTasks.length,
+ totalPages: Math.ceil(filteredTasks.length / size),
+ size: parseInt(size),
+ number: parseInt(page),
+ first: page == 0,
+ last: page >= Math.ceil(filteredTasks.length / size) - 1,
+ },
+ });
+ });
+
+ // 创建标注任务
+ router.post(API.createAnnotationTaskUsingPost, (req, res) => {
+ const newTask = {
+ ...annotationTaskItem(),
+ ...req.body,
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ status: "PENDING",
+ progress: 0,
+ annotatedCount: 0,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ };
+ annotationTaskList.push(newTask);
+
+ res.status(201).send({
+ code: "0",
+ msg: "Annotation task created successfully",
+ data: newTask,
+ });
+ });
+
+ // 获取标注任务详情
+ router.get(API.queryAnnotationTaskByIdUsingGet, (req, res) => {
+ const { taskId } = req.params;
+ const task = annotationTaskList.find((t) => t.id === taskId);
+
+ if (task) {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: task,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Annotation task not found",
+ data: null,
+ });
+ }
+ });
+
+ // 更新标注任务
+ router.put(API.syncAnnotationTaskByIdUsingPost, (req, res) => {
+ const { taskId } = req.params;
+ const index = annotationTaskList.findIndex((t) => t.id === taskId);
+
+ if (index !== -1) {
+ annotationTaskList[index] = {
+ ...annotationTaskList[index],
+ ...req.body,
+ updatedAt: new Date().toISOString(),
+ };
+ res.send({
+ code: "0",
+ msg: "Annotation task updated successfully",
+ data: annotationTaskList[index],
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Annotation task not found",
+ data: null,
+ });
+ }
+ });
+
+ // 删除标注任务
+ router.delete(API.deleteAnnotationTaskByIdUsingDelete, (req, res) => {
+ const { taskId } = req.params;
+ const index = annotationTaskList.findIndex((t) => t.id === taskId);
+
+ if (index !== -1) {
+ annotationTaskList.splice(index, 1);
+ res.send({
+ code: "0",
+ msg: "Annotation task deleted successfully",
+ data: null,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Annotation task not found",
+ data: null,
+ });
+ }
+ });
+
+ // 获取标注数据列表
+ router.get(API.queryAnnotationDataUsingGet, (req, res) => {
+ const { taskId } = req.params;
+ const { page = 0, size = 20, status } = req.query;
+
+ let filteredData = annotationDataList.filter(
+ (data) => data.taskId === taskId
+ );
+
+ if (status) {
+ filteredData = filteredData.filter((data) => data.status === status);
+ }
+
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = filteredData.slice(startIndex, endIndex);
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: pageData,
+ totalElements: filteredData.length,
+ totalPages: Math.ceil(filteredData.length / size),
+ size: parseInt(size),
+ number: parseInt(page),
+ },
+ });
+ });
+
+ // 提交标注
+ router.post(API.submitAnnotationUsingPost, (req, res) => {
+ const { taskId } = req.params;
+ const newAnnotation = {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ taskId,
+ ...req.body,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ };
+
+ res.status(201).send({
+ code: "0",
+ msg: "Annotation submitted successfully",
+ data: newAnnotation,
+ });
+ });
+
+ // 更新标注
+ router.put(API.updateAnnotationUsingPut, (req, res) => {
+ const { taskId, annotationId } = req.params;
+
+ res.send({
+ code: "0",
+ msg: "Annotation updated successfully",
+ data: {
+ id: annotationId,
+ taskId,
+ ...req.body,
+ updatedAt: new Date().toISOString(),
+ },
+ });
+ });
+
+ // 删除标注
+ router.delete(API.deleteAnnotationUsingDelete, (req, res) => {
+ const { taskId, annotationId } = req.params;
+
+ res.send({
+ code: "0",
+ msg: "Annotation deleted successfully",
+ data: null,
+ });
+ });
+
+ // 开始标注任务
+ router.post(API.startAnnotationTaskUsingPost, (req, res) => {
+ const { taskId } = req.params;
+ const task = annotationTaskList.find((t) => t.id === taskId);
+
+ if (task) {
+ task.status = "IN_PROGRESS";
+ task.updatedAt = new Date().toISOString();
+
+ res.send({
+ code: "0",
+ msg: "Annotation task started successfully",
+ data: task,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Annotation task not found",
+ data: null,
+ });
+ }
+ });
+
+ // 暂停标注任务
+ router.post(API.pauseAnnotationTaskUsingPost, (req, res) => {
+ const { taskId } = req.params;
+ const task = annotationTaskList.find((t) => t.id === taskId);
+
+ if (task) {
+ task.status = "PAUSED";
+ task.updatedAt = new Date().toISOString();
+
+ res.send({
+ code: "0",
+ msg: "Annotation task paused successfully",
+ data: task,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Annotation task not found",
+ data: null,
+ });
+ }
+ });
+
+ // 恢复标注任务
+ router.post(API.resumeAnnotationTaskUsingPost, (req, res) => {
+ const { taskId } = req.params;
+ const task = annotationTaskList.find((t) => t.id === taskId);
+
+ if (task) {
+ task.status = "IN_PROGRESS";
+ task.updatedAt = new Date().toISOString();
+
+ res.send({
+ code: "0",
+ msg: "Annotation task resumed successfully",
+ data: task,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Annotation task not found",
+ data: null,
+ });
+ }
+ });
+
+ // 完成标注任务
+ router.post(API.completeAnnotationTaskUsingPost, (req, res) => {
+ const { taskId } = req.params;
+ const task = annotationTaskList.find((t) => t.id === taskId);
+
+ if (task) {
+ task.status = "COMPLETED";
+ task.progress = 100;
+ task.updatedAt = new Date().toISOString();
+
+ res.send({
+ code: "0",
+ msg: "Annotation task completed successfully",
+ data: task,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Annotation task not found",
+ data: null,
+ });
+ }
+ });
+
+ // 获取标注任务统计信息
+ router.get(API.getAnnotationTaskStatisticsUsingGet, (req, res) => {
+ const { taskId } = req.params;
+ const task = annotationTaskList.find((t) => t.id === taskId);
+
+ if (task) {
+ const statistics = {
+ taskId,
+ totalDataCount: task.totalDataCount,
+ annotatedCount: task.annotatedCount,
+ progress: task.progress,
+ accuracy: task.statistics.accuracy,
+ averageAnnotationTime: task.statistics.averageTime,
+ reviewCount: task.statistics.reviewCount,
+ qualityScore: Mock.Random.float(0.8, 0.99, 2, 2),
+ annotatorDistribution: {
+ [Mock.Random.cname()]: Mock.Random.integer(10, 100),
+ [Mock.Random.cname()]: Mock.Random.integer(10, 100),
+ [Mock.Random.cname()]: Mock.Random.integer(10, 100),
+ },
+ };
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: statistics,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Annotation task not found",
+ data: null,
+ });
+ }
+ });
+
+ // 获取整体标注统计信息
+ router.get(API.getAnnotationStatisticsUsingGet, (req, res) => {
+ const statistics = {
+ totalTasks: annotationTaskList.length,
+ completedTasks: annotationTaskList.filter((t) => t.status === "COMPLETED")
+ .length,
+ inProgressTasks: annotationTaskList.filter(
+ (t) => t.status === "IN_PROGRESS"
+ ).length,
+ pendingTasks: annotationTaskList.filter((t) => t.status === "PENDING")
+ .length,
+ totalAnnotations: annotationDataList.length,
+ totalAnnotators: annotatorList.length,
+ averageAccuracy: Mock.Random.float(0.85, 0.95, 2, 2),
+ taskTypeDistribution: {
+ TEXT_CLASSIFICATION: Mock.Random.integer(5, 15),
+ NAMED_ENTITY_RECOGNITION: Mock.Random.integer(3, 10),
+ OBJECT_DETECTION: Mock.Random.integer(2, 8),
+ SEMANTIC_SEGMENTATION: Mock.Random.integer(1, 5),
+ },
+ };
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: statistics,
+ });
+ });
+
+ // 获取标注模板列表
+ router.get(API.queryAnnotationTemplatesUsingGet, (req, res) => {
+ const { page = 0, size = 20, type } = req.query;
+ let filteredTemplates = annotationTemplateList;
+
+ if (type) {
+ filteredTemplates = filteredTemplates.filter(
+ (template) => template.type === type
+ );
+ }
+
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = filteredTemplates.slice(startIndex, endIndex);
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: pageData,
+ totalElements: filteredTemplates.length,
+ totalPages: Math.ceil(filteredTemplates.length / size),
+ size: parseInt(size),
+ number: parseInt(page),
+ },
+ });
+ });
+
+ // 创建标注模板
+ router.post(API.createAnnotationTemplateUsingPost, (req, res) => {
+ const newTemplate = {
+ ...annotationTemplateItem(),
+ ...req.body,
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ usageCount: 0,
+ createdAt: new Date().toISOString(),
+ };
+ annotationTemplateList.push(newTemplate);
+
+ res.status(201).send({
+ code: "0",
+ msg: "Annotation template created successfully",
+ data: newTemplate,
+ });
+ });
+
+ // 获取标注模板详情
+ router.get(API.queryAnnotationTemplateByIdUsingGet, (req, res) => {
+ const { templateId } = req.params;
+ const template = annotationTemplateList.find((t) => t.id === templateId);
+
+ if (template) {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: template,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Annotation template not found",
+ data: null,
+ });
+ }
+ });
+
+ // 获取标注者列表
+ router.get(API.queryAnnotatorsUsingGet, (req, res) => {
+ const { page = 0, size = 20, status, skillLevel } = req.query;
+ let filteredAnnotators = annotatorList;
+
+ if (status) {
+ filteredAnnotators = filteredAnnotators.filter(
+ (annotator) => annotator.status === status
+ );
+ }
+
+ if (skillLevel) {
+ filteredAnnotators = filteredAnnotators.filter(
+ (annotator) => annotator.skillLevel === skillLevel
+ );
+ }
+
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = filteredAnnotators.slice(startIndex, endIndex);
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: pageData,
+ totalElements: filteredAnnotators.length,
+ totalPages: Math.ceil(filteredAnnotators.length / size),
+ size: parseInt(size),
+ number: parseInt(page),
+ },
+ });
+ });
+
+ // 分配标注者
+ router.post(API.assignAnnotatorUsingPost, (req, res) => {
+ const { taskId } = req.params;
+ const { annotatorIds } = req.body;
+
+ res.send({
+ code: "0",
+ msg: "Annotators assigned successfully",
+ data: {
+ taskId,
+ assignedAnnotators: annotatorIds,
+ assignedAt: new Date().toISOString(),
+ },
+ });
+ });
+};
diff --git a/frontend/src/mock/mock-seed/data-cleansing.cjs b/frontend/src/mock/mock-seed/data-cleansing.cjs
index 7f4542a10..6a1784975 100644
--- a/frontend/src/mock/mock-seed/data-cleansing.cjs
+++ b/frontend/src/mock/mock-seed/data-cleansing.cjs
@@ -1,563 +1,563 @@
-const Mock = require("mockjs");
-const API = require("../mock-apis.cjs");
-
-function operatorItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.ctitle(3, 10),
- description: Mock.Random.csentence(5, 20),
- version: "1.0.0",
- inputs: Mock.Random.integer(1, 5),
- outputs: Mock.Random.integer(1, 5),
- settings: JSON.stringify({
- host: { type: "input", name: "主机地址", defaultVal: "localhost" },
- fileLength: {
- name: "文档字数",
- description:
- "过滤字数不在指定范围内的文档,如[10,10000000]。若输入为空,则不对字数上/下限做限制。",
- type: "range",
- defaultVal: [10, 10000000],
- min: 0,
- max: 10000000000000000,
- step: 1,
- },
- range: {
- type: "range",
- name: "读取行数",
- description: "某个词的统计数/文档总词数 > 设定值,该文档被去除。",
- properties: [
- {
- name: "起始行",
- type: "inputNumber",
- defaultVal: 1000,
- min: 100,
- max: 10000,
- step: 1,
- },
- {
- name: "结束行",
- type: "inputNumber",
- defaultVal: 2000,
- min: 100,
- max: 10000,
- step: 1,
- },
- ],
- },
- filepath: { type: "input", name: "文件路径", defaultVal: "/path" },
- encoding: {
- type: "select",
- name: "编码",
- defaultVal: "utf-8",
- options: ["utf-8", "gbk", "ascii"],
- },
- radio: {
- type: "radio",
- name: "radio",
- defaultVal: "utf-8",
- options: ["utf-8", "gbk", "ascii"],
- },
- features: {
- type: "checkbox",
- name: "特征列",
- defaultVal: ["feature1", "feature3"],
- options: ["feature1", "feature2", "feature3"],
- },
- repeatPhraseRatio: {
- name: "文档词重复率",
- description: "某个词的统计数/文档总词数 > 设定值,该文档被去除。",
- type: "slider",
- defaultVal: 0.5,
- min: 0,
- max: 1,
- step: 0.1,
- },
- hitStopwords: {
- name: "去除停用词",
- description: "统计重复词时,选择是否要去除停用词。",
- type: "switch",
- defaultVal: false,
- required: true,
- checkedLabel: "去除",
- unCheckedLabel: "不去除",
- },
- }),
- categories: [Mock.Random.pick([3, 4, 5, 6, 7, 8, 9])],
- isStar: Mock.Random.boolean(),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- };
-}
-
-const operatorList = new Array(50).fill(null).map(operatorItem);
-
-// 清洗任务数据
-function cleaningTaskItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.ctitle(5, 20),
- description: Mock.Random.csentence(5, 30),
- status: Mock.Random.pick([
- "PENDING",
- "RUNNING",
- "COMPLETED",
- "FAILED",
- "STOPPED",
- ]),
- srcDatasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- srcDatasetName: Mock.Random.ctitle(5, 15),
- destDatasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- destDatasetName: Mock.Random.ctitle(5, 15),
- progress: {
- finishedFileNum: Mock.Random.integer(0, 100),
- process: Mock.Random.integer(0, 100),
- totalFileNum: 100,
- },
- startedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- finishedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- instance: operatorList,
- };
-}
-
-const cleaningTaskList = new Array(20).fill(null).map(cleaningTaskItem);
-
-// 清洗模板数据
-function cleaningTemplateItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.ctitle(5, 15),
- description: Mock.Random.csentence(5, 25),
- instance: operatorList.slice(
- Mock.Random.integer(0, 5),
- Mock.Random.integer(6, 50)
- ),
- category: Mock.Random.ctitle(3, 8),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- };
-}
-
-const cleaningTemplateList = new Array(15).fill(null).map(cleaningTemplateItem);
-
-const categoryTree = [
- {
- id: 1,
- name: "modal",
- count: 7,
- categories: [
- { id: 3, name: "text", count: 3, type: null, parentId: null },
- { id: 4, name: "image", count: 0, type: null, parentId: null },
- { id: 5, name: "audio", count: 0, type: null, parentId: null },
- { id: 6, name: "video", count: 0, type: null, parentId: null },
- {
- id: 7,
- name: "multimodal",
- count: 0,
- type: null,
- parentId: null,
- },
- ],
- },
- {
- id: 2,
- name: "language",
- count: 3,
- categories: [
- { id: 8, name: "python", count: 2, type: null, parentId: null },
- { id: 9, name: "java", count: 1, type: null, parentId: null },
- ],
- },
-];
-
-module.exports = function (router) {
- // 获取清洗任务列表
- router.get(API.queryCleaningTasksUsingGet, (req, res) => {
- const { page = 0, size = 10, status } = req.query;
- let filteredTasks = cleaningTaskList;
-
- if (status) {
- filteredTasks = cleaningTaskList.filter((task) => task.status === status);
- }
-
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = filteredTasks.slice(startIndex, endIndex);
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- content: pageData,
- totalElements: filteredTasks.length,
- totalPages: Math.ceil(filteredTasks.length / size),
- size: parseInt(size),
- number: parseInt(page),
- },
- });
- });
-
- // 创建清洗任务
- router.post(API.createCleaningTaskUsingPost, (req, res) => {
- const newTask = {
- ...cleaningTaskItem(),
- ...req.body,
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- status: "PENDING",
- createdAt: new Date().toISOString(),
- };
- cleaningTaskList.push(newTask);
-
- res.status(201).send({
- code: "0",
- msg: "Cleaning task created successfully",
- data: newTask,
- });
- });
-
- // 获取清洗任务详情
- router.get(API.queryCleaningTaskByIdUsingGet, (req, res) => {
- const { taskId } = req.params;
- const task = cleaningTaskList.find((j) => j.id === taskId);
-
- if (task) {
- res.send({
- code: "0",
- msg: "Success",
- data: task,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Cleaning task not found",
- data: null,
- });
- }
- });
-
- // 删除清洗任务
- router.delete(API.deleteCleaningTaskByIdUsingDelete, (req, res) => {
- const { taskId } = req.params;
- const index = cleaningTaskList.findIndex((j) => j.id === taskId);
-
- if (index !== -1) {
- cleaningTaskList.splice(index, 1);
- res.send({
- code: "0",
- msg: "Cleaning task deleted successfully",
- data: null,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Cleaning task not found",
- data: null,
- });
- }
- });
-
- // 执行清洗任务
- router.post(API.executeCleaningTaskUsingPost, (req, res) => {
- const { taskId } = req.params;
- const task = cleaningTaskList.find((j) => j.id === taskId);
-
- if (task) {
- task.status = "RUNNING";
- task.startTime = new Date().toISOString();
-
- res.send({
- code: "0",
- msg: "Cleaning task execution started",
- data: {
- executionId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- status: "RUNNING",
- message: "Task execution started successfully",
- },
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Cleaning task not found",
- data: null,
- });
- }
- });
-
- // 停止清洗任务
- router.post(API.stopCleaningTaskUsingPost, (req, res) => {
- const { taskId } = req.params;
- const task = cleaningTaskList.find((j) => j.id === taskId);
-
- if (task) {
- task.status = "PENDING";
- task.endTime = new Date().toISOString();
-
- res.send({
- code: "0",
- msg: "Cleaning task stopped successfully",
- data: null,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Cleaning task not found",
- data: null,
- });
- }
- });
-
- // 获取清洗模板列表
- router.get(API.queryCleaningTemplatesUsingGet, (req, res) => {
- const { page = 0, size = 20 } = req.query;
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = cleaningTemplateList.slice(startIndex, endIndex);
- res.send({
- code: "0",
- msg: "Success",
- data: { content: pageData, totalElements: cleaningTemplateList.length },
- });
- });
-
- // 创建清洗模板
- router.post(API.createCleaningTemplateUsingPost, (req, res) => {
- const newTemplate = {
- ...cleaningTemplateItem(),
- ...req.body,
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- createdAt: new Date().toISOString(),
- };
- cleaningTemplateList.push(newTemplate);
-
- res.status(201).send({
- code: "0",
- msg: "Cleaning template created successfully",
- data: newTemplate,
- });
- });
-
- // 获取清洗模板详情
- router.get(API.queryCleaningTemplateByIdUsingGet, (req, res) => {
- const { templateId } = req.params;
- const template = cleaningTemplateList.find((t) => t.id === templateId);
-
- if (template) {
- res.send({
- code: "0",
- msg: "Success",
- data: template,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Cleaning template not found",
- data: null,
- });
- }
- });
-
- // 删除清洗模板
- router.delete(API.deleteCleaningTemplateByIdUsingDelete, (req, res) => {
- const { templateId } = req.params;
- const index = cleaningTemplateList.findIndex((t) => t.id === templateId);
-
- if (index !== -1) {
- cleaningTemplateList.splice(index, 1);
- res.send({
- code: "0",
- msg: "Cleaning template deleted successfully",
- data: null,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Cleaning template not found",
- data: null,
- });
- }
- });
-
- // 获取算子列表
- router.post(API.queryOperatorsUsingPost, (req, res) => {
- const {
- page = 0,
- size = 20,
- categories = [],
- operatorName = "",
- labelName = "",
- isStar,
- } = req.body;
-
- let filteredOperators = operatorList;
-
- // 按分类筛选
- if (categories && categories.length > 0) {
- filteredOperators = filteredOperators.filter((op) =>
- categories.includes(op.category.id)
- );
- }
-
- // 按名称搜索
- if (operatorName) {
- filteredOperators = filteredOperators.filter((op) =>
- op.name.toLowerCase().includes(operatorName.toLowerCase())
- );
- }
-
- // 按标签筛选
- if (labelName) {
- filteredOperators = filteredOperators.filter((op) =>
- op.labels.some((label) => label.name.includes(labelName))
- );
- }
-
- // 按收藏状态筛选
- if (typeof isStar === "boolean") {
- filteredOperators = filteredOperators.filter(
- (op) => op.isStar === isStar
- );
- }
-
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = filteredOperators.slice(startIndex, endIndex);
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- content: pageData,
- totalElements: filteredOperators.length,
- totalPages: Math.ceil(filteredOperators.length / size),
- size: parseInt(size),
- number: parseInt(page),
- first: page === 0,
- last: page >= Math.ceil(filteredOperators.length / size) - 1,
- },
- });
- });
-
- // 获取算子详情
- router.get(API.queryOperatorByIdUsingGet, (req, res) => {
- const { id } = req.params;
- const operator = operatorList.find((op) => op.id === id);
- console.log("获取算子详情:", id, operator);
- if (operator) {
- // 增加浏览次数模拟
- operator.viewCount = (operator.viewCount || 0) + 1;
-
- res.send({
- code: "0",
- msg: "Success",
- data: operator,
- });
- } else {
- res.status(404).send({
- error: "OPERATOR_NOT_FOUND",
- message: "算子不存在",
- timestamp: new Date().toISOString(),
- });
- }
- });
-
- // 更新算子信息
- router.put(API.updateOperatorByIdUsingPut, (req, res) => {
- const { id } = req.params;
- const index = operatorList.findIndex((op) => op.id === id);
-
- if (index !== -1) {
- operatorList[index] = {
- ...operatorList[index],
- ...req.body,
- updatedAt: new Date().toISOString(),
- };
-
- res.send({
- code: "0",
- msg: "Operator updated successfully",
- data: operatorList[index],
- });
- } else {
- res.status(404).send({
- error: "OPERATOR_NOT_FOUND",
- message: "算子不存在",
- timestamp: new Date().toISOString(),
- });
- }
- });
-
- // 创建算子
- router.post(API.createOperatorUsingPost, (req, res) => {
- const { name, description, version, category, documentation } = req.body;
-
- const newOperator = {
- ...operatorItem(),
- ...req.body,
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name,
- description,
- version,
- category:
- typeof category === "string"
- ? { id: category, name: category }
- : category,
- documentation,
- status: "REVIEWING",
- downloadCount: 0,
- rating: 0,
- ratingCount: 0,
- isStar: false,
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString(),
- };
-
- operatorList.push(newOperator);
-
- res.status(201).send({
- code: "0",
- msg: "Operator created successfully",
- data: newOperator,
- });
- });
-
- // 上传算子
- router.post(API.uploadOperatorUsingPost, (req, res) => {
- const { description } = req.body;
-
- const newOperator = {
- ...operatorItem(),
- description: description || "通过文件上传创建的算子",
- status: "REVIEWING",
- downloadCount: 0,
- rating: 0,
- ratingCount: 0,
- isStar: false,
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString(),
- };
-
- operatorList.push(newOperator);
-
- res.status(201).send({
- code: "0",
- msg: "Operator uploaded successfully",
- data: newOperator,
- });
- });
-
- // 获取算子分类树
- router.get(API.queryCategoryTreeUsingGet, (req, res) => {
- res.send({
- code: "0",
- msg: "Success",
- data: {
- page: 0,
- size: categoryTree.length,
- totalElements: categoryTree.length,
- totalPages: 1,
- content: categoryTree,
- },
- });
- });
-};
+const Mock = require("mockjs");
+const API = require("../mock-apis.cjs");
+
+function operatorItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.ctitle(3, 10),
+ description: Mock.Random.csentence(5, 20),
+ version: "1.0.0",
+ inputs: Mock.Random.integer(1, 5),
+ outputs: Mock.Random.integer(1, 5),
+ settings: JSON.stringify({
+ host: { type: "input", name: "主机地址", defaultVal: "localhost" },
+ fileLength: {
+ name: "文档字数",
+ description:
+ "过滤字数不在指定范围内的文档,如[10,10000000]。若输入为空,则不对字数上/下限做限制。",
+ type: "range",
+ defaultVal: [10, 10000000],
+ min: 0,
+ max: 10000000000000000,
+ step: 1,
+ },
+ range: {
+ type: "range",
+ name: "读取行数",
+ description: "某个词的统计数/文档总词数 > 设定值,该文档被去除。",
+ properties: [
+ {
+ name: "起始行",
+ type: "inputNumber",
+ defaultVal: 1000,
+ min: 100,
+ max: 10000,
+ step: 1,
+ },
+ {
+ name: "结束行",
+ type: "inputNumber",
+ defaultVal: 2000,
+ min: 100,
+ max: 10000,
+ step: 1,
+ },
+ ],
+ },
+ filepath: { type: "input", name: "文件路径", defaultVal: "/path" },
+ encoding: {
+ type: "select",
+ name: "编码",
+ defaultVal: "utf-8",
+ options: ["utf-8", "gbk", "ascii"],
+ },
+ radio: {
+ type: "radio",
+ name: "radio",
+ defaultVal: "utf-8",
+ options: ["utf-8", "gbk", "ascii"],
+ },
+ features: {
+ type: "checkbox",
+ name: "特征列",
+ defaultVal: ["feature1", "feature3"],
+ options: ["feature1", "feature2", "feature3"],
+ },
+ repeatPhraseRatio: {
+ name: "文档词重复率",
+ description: "某个词的统计数/文档总词数 > 设定值,该文档被去除。",
+ type: "slider",
+ defaultVal: 0.5,
+ min: 0,
+ max: 1,
+ step: 0.1,
+ },
+ hitStopwords: {
+ name: "去除停用词",
+ description: "统计重复词时,选择是否要去除停用词。",
+ type: "switch",
+ defaultVal: false,
+ required: true,
+ checkedLabel: "去除",
+ unCheckedLabel: "不去除",
+ },
+ }),
+ categories: [Mock.Random.pick([3, 4, 5, 6, 7, 8, 9])],
+ isStar: Mock.Random.boolean(),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ };
+}
+
+const operatorList = new Array(50).fill(null).map(operatorItem);
+
+// 清洗任务数据
+function cleaningTaskItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.ctitle(5, 20),
+ description: Mock.Random.csentence(5, 30),
+ status: Mock.Random.pick([
+ "PENDING",
+ "RUNNING",
+ "COMPLETED",
+ "FAILED",
+ "STOPPED",
+ ]),
+ srcDatasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ srcDatasetName: Mock.Random.ctitle(5, 15),
+ destDatasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ destDatasetName: Mock.Random.ctitle(5, 15),
+ progress: {
+ finishedFileNum: Mock.Random.integer(0, 100),
+ process: Mock.Random.integer(0, 100),
+ totalFileNum: 100,
+ },
+ startedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ finishedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ instance: operatorList,
+ };
+}
+
+const cleaningTaskList = new Array(20).fill(null).map(cleaningTaskItem);
+
+// 清洗模板数据
+function cleaningTemplateItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.ctitle(5, 15),
+ description: Mock.Random.csentence(5, 25),
+ instance: operatorList.slice(
+ Mock.Random.integer(0, 5),
+ Mock.Random.integer(6, 50)
+ ),
+ category: Mock.Random.ctitle(3, 8),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ };
+}
+
+const cleaningTemplateList = new Array(15).fill(null).map(cleaningTemplateItem);
+
+const categoryTree = [
+ {
+ id: 1,
+ name: "modal",
+ count: 7,
+ categories: [
+ { id: 3, name: "text", count: 3, type: null, parentId: null },
+ { id: 4, name: "image", count: 0, type: null, parentId: null },
+ { id: 5, name: "audio", count: 0, type: null, parentId: null },
+ { id: 6, name: "video", count: 0, type: null, parentId: null },
+ {
+ id: 7,
+ name: "multimodal",
+ count: 0,
+ type: null,
+ parentId: null,
+ },
+ ],
+ },
+ {
+ id: 2,
+ name: "language",
+ count: 3,
+ categories: [
+ { id: 8, name: "python", count: 2, type: null, parentId: null },
+ { id: 9, name: "java", count: 1, type: null, parentId: null },
+ ],
+ },
+];
+
+module.exports = function (router) {
+ // 获取清洗任务列表
+ router.get(API.queryCleaningTasksUsingGet, (req, res) => {
+ const { page = 0, size = 10, status } = req.query;
+ let filteredTasks = cleaningTaskList;
+
+ if (status) {
+ filteredTasks = cleaningTaskList.filter((task) => task.status === status);
+ }
+
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = filteredTasks.slice(startIndex, endIndex);
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: pageData,
+ totalElements: filteredTasks.length,
+ totalPages: Math.ceil(filteredTasks.length / size),
+ size: parseInt(size),
+ number: parseInt(page),
+ },
+ });
+ });
+
+ // 创建清洗任务
+ router.post(API.createCleaningTaskUsingPost, (req, res) => {
+ const newTask = {
+ ...cleaningTaskItem(),
+ ...req.body,
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ status: "PENDING",
+ createdAt: new Date().toISOString(),
+ };
+ cleaningTaskList.push(newTask);
+
+ res.status(201).send({
+ code: "0",
+ msg: "Cleaning task created successfully",
+ data: newTask,
+ });
+ });
+
+ // 获取清洗任务详情
+ router.get(API.queryCleaningTaskByIdUsingGet, (req, res) => {
+ const { taskId } = req.params;
+ const task = cleaningTaskList.find((j) => j.id === taskId);
+
+ if (task) {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: task,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Cleaning task not found",
+ data: null,
+ });
+ }
+ });
+
+ // 删除清洗任务
+ router.delete(API.deleteCleaningTaskByIdUsingDelete, (req, res) => {
+ const { taskId } = req.params;
+ const index = cleaningTaskList.findIndex((j) => j.id === taskId);
+
+ if (index !== -1) {
+ cleaningTaskList.splice(index, 1);
+ res.send({
+ code: "0",
+ msg: "Cleaning task deleted successfully",
+ data: null,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Cleaning task not found",
+ data: null,
+ });
+ }
+ });
+
+ // 执行清洗任务
+ router.post(API.executeCleaningTaskUsingPost, (req, res) => {
+ const { taskId } = req.params;
+ const task = cleaningTaskList.find((j) => j.id === taskId);
+
+ if (task) {
+ task.status = "RUNNING";
+ task.startTime = new Date().toISOString();
+
+ res.send({
+ code: "0",
+ msg: "Cleaning task execution started",
+ data: {
+ executionId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ status: "RUNNING",
+ message: "Task execution started successfully",
+ },
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Cleaning task not found",
+ data: null,
+ });
+ }
+ });
+
+ // 停止清洗任务
+ router.post(API.stopCleaningTaskUsingPost, (req, res) => {
+ const { taskId } = req.params;
+ const task = cleaningTaskList.find((j) => j.id === taskId);
+
+ if (task) {
+ task.status = "PENDING";
+ task.endTime = new Date().toISOString();
+
+ res.send({
+ code: "0",
+ msg: "Cleaning task stopped successfully",
+ data: null,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Cleaning task not found",
+ data: null,
+ });
+ }
+ });
+
+ // 获取清洗模板列表
+ router.get(API.queryCleaningTemplatesUsingGet, (req, res) => {
+ const { page = 0, size = 20 } = req.query;
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = cleaningTemplateList.slice(startIndex, endIndex);
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: { content: pageData, totalElements: cleaningTemplateList.length },
+ });
+ });
+
+ // 创建清洗模板
+ router.post(API.createCleaningTemplateUsingPost, (req, res) => {
+ const newTemplate = {
+ ...cleaningTemplateItem(),
+ ...req.body,
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ createdAt: new Date().toISOString(),
+ };
+ cleaningTemplateList.push(newTemplate);
+
+ res.status(201).send({
+ code: "0",
+ msg: "Cleaning template created successfully",
+ data: newTemplate,
+ });
+ });
+
+ // 获取清洗模板详情
+ router.get(API.queryCleaningTemplateByIdUsingGet, (req, res) => {
+ const { templateId } = req.params;
+ const template = cleaningTemplateList.find((t) => t.id === templateId);
+
+ if (template) {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: template,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Cleaning template not found",
+ data: null,
+ });
+ }
+ });
+
+ // 删除清洗模板
+ router.delete(API.deleteCleaningTemplateByIdUsingDelete, (req, res) => {
+ const { templateId } = req.params;
+ const index = cleaningTemplateList.findIndex((t) => t.id === templateId);
+
+ if (index !== -1) {
+ cleaningTemplateList.splice(index, 1);
+ res.send({
+ code: "0",
+ msg: "Cleaning template deleted successfully",
+ data: null,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Cleaning template not found",
+ data: null,
+ });
+ }
+ });
+
+ // 获取算子列表
+ router.post(API.queryOperatorsUsingPost, (req, res) => {
+ const {
+ page = 0,
+ size = 20,
+ categories = [],
+ operatorName = "",
+ labelName = "",
+ isStar,
+ } = req.body;
+
+ let filteredOperators = operatorList;
+
+ // 按分类筛选
+ if (categories && categories.length > 0) {
+ filteredOperators = filteredOperators.filter((op) =>
+ categories.includes(op.category.id)
+ );
+ }
+
+ // 按名称搜索
+ if (operatorName) {
+ filteredOperators = filteredOperators.filter((op) =>
+ op.name.toLowerCase().includes(operatorName.toLowerCase())
+ );
+ }
+
+ // 按标签筛选
+ if (labelName) {
+ filteredOperators = filteredOperators.filter((op) =>
+ op.labels.some((label) => label.name.includes(labelName))
+ );
+ }
+
+ // 按收藏状态筛选
+ if (typeof isStar === "boolean") {
+ filteredOperators = filteredOperators.filter(
+ (op) => op.isStar === isStar
+ );
+ }
+
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = filteredOperators.slice(startIndex, endIndex);
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: pageData,
+ totalElements: filteredOperators.length,
+ totalPages: Math.ceil(filteredOperators.length / size),
+ size: parseInt(size),
+ number: parseInt(page),
+ first: page === 0,
+ last: page >= Math.ceil(filteredOperators.length / size) - 1,
+ },
+ });
+ });
+
+ // 获取算子详情
+ router.get(API.queryOperatorByIdUsingGet, (req, res) => {
+ const { id } = req.params;
+ const operator = operatorList.find((op) => op.id === id);
+ console.log("获取算子详情:", id, operator);
+ if (operator) {
+ // 增加浏览次数模拟
+ operator.viewCount = (operator.viewCount || 0) + 1;
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: operator,
+ });
+ } else {
+ res.status(404).send({
+ error: "OPERATOR_NOT_FOUND",
+ message: "算子不存在",
+ timestamp: new Date().toISOString(),
+ });
+ }
+ });
+
+ // 更新算子信息
+ router.put(API.updateOperatorByIdUsingPut, (req, res) => {
+ const { id } = req.params;
+ const index = operatorList.findIndex((op) => op.id === id);
+
+ if (index !== -1) {
+ operatorList[index] = {
+ ...operatorList[index],
+ ...req.body,
+ updatedAt: new Date().toISOString(),
+ };
+
+ res.send({
+ code: "0",
+ msg: "Operator updated successfully",
+ data: operatorList[index],
+ });
+ } else {
+ res.status(404).send({
+ error: "OPERATOR_NOT_FOUND",
+ message: "算子不存在",
+ timestamp: new Date().toISOString(),
+ });
+ }
+ });
+
+ // 创建算子
+ router.post(API.createOperatorUsingPost, (req, res) => {
+ const { name, description, version, category, documentation } = req.body;
+
+ const newOperator = {
+ ...operatorItem(),
+ ...req.body,
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name,
+ description,
+ version,
+ category:
+ typeof category === "string"
+ ? { id: category, name: category }
+ : category,
+ documentation,
+ status: "REVIEWING",
+ downloadCount: 0,
+ rating: 0,
+ ratingCount: 0,
+ isStar: false,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ };
+
+ operatorList.push(newOperator);
+
+ res.status(201).send({
+ code: "0",
+ msg: "Operator created successfully",
+ data: newOperator,
+ });
+ });
+
+ // 上传算子
+ router.post(API.uploadOperatorUsingPost, (req, res) => {
+ const { description } = req.body;
+
+ const newOperator = {
+ ...operatorItem(),
+ description: description || "通过文件上传创建的算子",
+ status: "REVIEWING",
+ downloadCount: 0,
+ rating: 0,
+ ratingCount: 0,
+ isStar: false,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ };
+
+ operatorList.push(newOperator);
+
+ res.status(201).send({
+ code: "0",
+ msg: "Operator uploaded successfully",
+ data: newOperator,
+ });
+ });
+
+ // 获取算子分类树
+ router.get(API.queryCategoryTreeUsingGet, (req, res) => {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ page: 0,
+ size: categoryTree.length,
+ totalElements: categoryTree.length,
+ totalPages: 1,
+ content: categoryTree,
+ },
+ });
+ });
+};
diff --git a/frontend/src/mock/mock-seed/data-collection.cjs b/frontend/src/mock/mock-seed/data-collection.cjs
index f7670ba86..8998b0b6b 100644
--- a/frontend/src/mock/mock-seed/data-collection.cjs
+++ b/frontend/src/mock/mock-seed/data-collection.cjs
@@ -1,232 +1,232 @@
-const Mock = require("mockjs");
-const API = require("../mock-apis.cjs");
-const { Random } = Mock;
-
-// 生成模拟数据归集统计信息
-function dataXTemplate() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.ctitle(5, 15),
- sourceType: Mock.Random.csentence(3, 10),
- targetType: Mock.Random.csentence(3, 10),
- description: Mock.Random.csentence(5, 20),
- version: `v${Mock.Random.integer(1, 5)}.${Mock.Random.integer(
- 0,
- 9
- )}.${Mock.Random.integer(0, 9)}`,
- isSystem: Mock.Random.boolean(),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- };
-}
-
-const templateList = new Array(20).fill(null).map(dataXTemplate);
-
-// 生成模拟任务数据
-function taskItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.ctitle(5, 20),
- description: Mock.Random.csentence(5, 20),
- syncMode: Mock.Random.pick(["ONCE", "SCHEDULED"]),
- config: {
- query: "SELECT * FROM table WHERE condition",
- batchSize: Mock.Random.integer(100, 1000),
- frequency: Mock.Random.integer(1, 60), // in minutes
- },
- scheduleExpression: "0 0 * * *", // cron expression
- lastExecutionId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- status: Mock.Random.pick([
- "DRAFT",
- "READY",
- "RUNNING",
- "FAILED",
- "STOPPED",
- "SUCCESS",
- ]),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- sourceDataSourceId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- sourceDataSourceName: Mock.Random.ctitle(5, 20),
- targetDataSourceId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- targetDataSourceName: Mock.Random.ctitle(5, 20),
- };
-}
-
-const taskList = new Array(50).fill(null).map(taskItem);
-
-// 生成模拟任务执行日志数据
-function executionLogItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- taskName: Mock.Random.ctitle(5, 20),
- dataSource: Mock.Random.ctitle(5, 15),
- startTime: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- endTime: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- status: Mock.Random.pick(["SUCCESS", "FAILED", "RUNNING"]),
- triggerType: Mock.Random.pick(["MANUAL", "SCHEDULED", "API"]),
- duration: Mock.Random.integer(1, 120),
- retryCount: Mock.Random.integer(0, 5),
- recordsProcessed: Mock.Random.integer(100, 10000),
- processId: Mock.Random.integer(1000, 9999),
- errorMessage: Mock.Random.boolean() ? "" : Mock.Random.csentence(5, 20),
- };
-}
-
-const executionLogList = new Array(100).fill(null).map(executionLogItem);
-
-module.exports = function (router) {
- // 获取任务列表
- router.get(API.queryTasksUsingGet, (req, res) => {
- const { keyword, status, page = 0, size = 10 } = req.query;
- let filteredTasks = taskList;
- if (keyword) {
- filteredTasks = filteredTasks.filter((task) =>
- task.name.includes(keyword)
- );
- }
- if (status && status.length > 0) {
- filteredTasks = filteredTasks.filter((task) =>
- status.includes(task.status)
- );
- }
- const startIndex = page * size;
- const endIndex = startIndex + size;
- const paginatedTasks = filteredTasks.slice(startIndex, endIndex);
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- totalElements: filteredTasks.length,
- page,
- size,
- content: paginatedTasks,
- },
- });
- });
-
- router.get(API.queryDataXTemplatesUsingGet, (req, res) => {
- const { keyword, page = 0, size = 10 } = req.query;
- let filteredTemplates = templateList;
- if (keyword) {
- filteredTemplates = filteredTemplates.filter((template) =>
- template.name.includes(keyword)
- );
- }
- const startIndex = page * size;
- const endIndex = startIndex + size;
- const paginatedTemplates = filteredTemplates.slice(startIndex, endIndex);
- res.send({
- code: "0",
- msg: "Success",
- data: {
- content: paginatedTemplates,
- totalElements: filteredTemplates.length,
- page,
- size,
- },
- });
- });
-
- // 创建任务
- router.post(API.createTaskUsingPost, (req, res) => {
- taskList.unshift(taskItem()); // 添加一个新的任务到列表开头
- res.send({
- code: "0",
- msg: "任务创建成功",
- data: {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- },
- });
- });
-
- // 更新任务
- router.post(API.updateTaskByIdUsingPut, (req, res) => {
- const { id } = req.body;
- res.send({
- code: "0",
- msg: "Data source task updated successfully",
- data: taskList.find((task) => task.id === id),
- });
- });
-
- // 删除任务
- router.post(API.deleteTaskByIdUsingDelete, (req, res) => {
- const { id } = req.body;
- const index = taskList.findIndex((task) => task.id === id);
- if (index !== -1) {
- taskList.splice(index, 1);
- }
- res.send({
- code: "0",
- msg: "Data source task deleted successfully",
- data: null,
- });
- });
-
- // 执行任务
- router.post(API.executeTaskByIdUsingPost, (req, res) => {
- console.log("Received request to execute task", req.body);
- const { id } = req.body;
- console.log("Executing task with ID:", id);
- taskList.find((task) => task.id === id).status = "RUNNING";
- res.send({
- code: "0",
- msg: "Data source task execution started",
- data: null,
- });
- });
-
- // 停止任务
- router.post(API.stopTaskByIdUsingPost, (req, res) => {
- const { id } = req.body;
- const task = taskList.find((task) => task.id === id);
- if (task) {
- task.status = "STOPPED";
- }
- res.send({
- code: "0",
- msg: "Data source task stopped successfully",
- data: null,
- });
- });
-
- // 获取任务执行日志
- router.post(API.queryExecutionLogUsingPost, (req, res) => {
- const { keyword, page = 1, size = 10, status } = req.body;
- let filteredLogs = executionLogList;
- if (keyword) {
- filteredLogs = filteredLogs.filter((log) =>
- log.taskName.includes(keyword)
- );
- }
- if (status && status.length > 0) {
- filteredLogs = filteredLogs.filter((log) => status.includes(log.status));
- }
- const startIndex = (page - 1) * size;
- const endIndex = startIndex + size;
- const paginatedLogs = filteredLogs.slice(startIndex, endIndex);
- res.send({
- code: "0",
- msg: "Success",
- data: {
- totalElements: filteredLogs.length,
- page,
- size,
- results: paginatedLogs,
- },
- });
- });
-
- // 获取任务执行日志详情
- router.post(API.queryExecutionLogByIdUsingGet, (req, res) => {
- const { id } = req.body;
- const log = executionLogList.find((log) => log.id === id);
- res.send({
- code: "0",
- msg: "Success",
- data: log,
- });
- });
-};
+const Mock = require("mockjs");
+const API = require("../mock-apis.cjs");
+const { Random } = Mock;
+
+// 生成模拟数据归集统计信息
+function dataXTemplate() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.ctitle(5, 15),
+ sourceType: Mock.Random.csentence(3, 10),
+ targetType: Mock.Random.csentence(3, 10),
+ description: Mock.Random.csentence(5, 20),
+ version: `v${Mock.Random.integer(1, 5)}.${Mock.Random.integer(
+ 0,
+ 9
+ )}.${Mock.Random.integer(0, 9)}`,
+ isSystem: Mock.Random.boolean(),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ };
+}
+
+const templateList = new Array(20).fill(null).map(dataXTemplate);
+
+// 生成模拟任务数据
+function taskItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.ctitle(5, 20),
+ description: Mock.Random.csentence(5, 20),
+ syncMode: Mock.Random.pick(["ONCE", "SCHEDULED"]),
+ config: {
+ query: "SELECT * FROM table WHERE condition",
+ batchSize: Mock.Random.integer(100, 1000),
+ frequency: Mock.Random.integer(1, 60), // in minutes
+ },
+ scheduleExpression: "0 0 * * *", // cron expression
+ lastExecutionId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ status: Mock.Random.pick([
+ "DRAFT",
+ "READY",
+ "RUNNING",
+ "FAILED",
+ "STOPPED",
+ "SUCCESS",
+ ]),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ sourceDataSourceId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ sourceDataSourceName: Mock.Random.ctitle(5, 20),
+ targetDataSourceId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ targetDataSourceName: Mock.Random.ctitle(5, 20),
+ };
+}
+
+const taskList = new Array(50).fill(null).map(taskItem);
+
+// 生成模拟任务执行日志数据
+function executionLogItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ taskName: Mock.Random.ctitle(5, 20),
+ dataSource: Mock.Random.ctitle(5, 15),
+ startTime: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ endTime: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ status: Mock.Random.pick(["SUCCESS", "FAILED", "RUNNING"]),
+ triggerType: Mock.Random.pick(["MANUAL", "SCHEDULED", "API"]),
+ duration: Mock.Random.integer(1, 120),
+ retryCount: Mock.Random.integer(0, 5),
+ recordsProcessed: Mock.Random.integer(100, 10000),
+ processId: Mock.Random.integer(1000, 9999),
+ errorMessage: Mock.Random.boolean() ? "" : Mock.Random.csentence(5, 20),
+ };
+}
+
+const executionLogList = new Array(100).fill(null).map(executionLogItem);
+
+module.exports = function (router) {
+ // 获取任务列表
+ router.get(API.queryTasksUsingGet, (req, res) => {
+ const { keyword, status, page = 0, size = 10 } = req.query;
+ let filteredTasks = taskList;
+ if (keyword) {
+ filteredTasks = filteredTasks.filter((task) =>
+ task.name.includes(keyword)
+ );
+ }
+ if (status && status.length > 0) {
+ filteredTasks = filteredTasks.filter((task) =>
+ status.includes(task.status)
+ );
+ }
+ const startIndex = page * size;
+ const endIndex = startIndex + size;
+ const paginatedTasks = filteredTasks.slice(startIndex, endIndex);
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ totalElements: filteredTasks.length,
+ page,
+ size,
+ content: paginatedTasks,
+ },
+ });
+ });
+
+ router.get(API.queryDataXTemplatesUsingGet, (req, res) => {
+ const { keyword, page = 0, size = 10 } = req.query;
+ let filteredTemplates = templateList;
+ if (keyword) {
+ filteredTemplates = filteredTemplates.filter((template) =>
+ template.name.includes(keyword)
+ );
+ }
+ const startIndex = page * size;
+ const endIndex = startIndex + size;
+ const paginatedTemplates = filteredTemplates.slice(startIndex, endIndex);
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: paginatedTemplates,
+ totalElements: filteredTemplates.length,
+ page,
+ size,
+ },
+ });
+ });
+
+ // 创建任务
+ router.post(API.createTaskUsingPost, (req, res) => {
+ taskList.unshift(taskItem()); // 添加一个新的任务到列表开头
+ res.send({
+ code: "0",
+ msg: "任务创建成功",
+ data: {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ },
+ });
+ });
+
+ // 更新任务
+ router.post(API.updateTaskByIdUsingPut, (req, res) => {
+ const { id } = req.body;
+ res.send({
+ code: "0",
+ msg: "Data source task updated successfully",
+ data: taskList.find((task) => task.id === id),
+ });
+ });
+
+ // 删除任务
+ router.post(API.deleteTaskByIdUsingDelete, (req, res) => {
+ const { id } = req.body;
+ const index = taskList.findIndex((task) => task.id === id);
+ if (index !== -1) {
+ taskList.splice(index, 1);
+ }
+ res.send({
+ code: "0",
+ msg: "Data source task deleted successfully",
+ data: null,
+ });
+ });
+
+ // 执行任务
+ router.post(API.executeTaskByIdUsingPost, (req, res) => {
+ console.log("Received request to execute task", req.body);
+ const { id } = req.body;
+ console.log("Executing task with ID:", id);
+ taskList.find((task) => task.id === id).status = "RUNNING";
+ res.send({
+ code: "0",
+ msg: "Data source task execution started",
+ data: null,
+ });
+ });
+
+ // 停止任务
+ router.post(API.stopTaskByIdUsingPost, (req, res) => {
+ const { id } = req.body;
+ const task = taskList.find((task) => task.id === id);
+ if (task) {
+ task.status = "STOPPED";
+ }
+ res.send({
+ code: "0",
+ msg: "Data source task stopped successfully",
+ data: null,
+ });
+ });
+
+ // 获取任务执行日志
+ router.post(API.queryExecutionLogUsingPost, (req, res) => {
+ const { keyword, page = 1, size = 10, status } = req.body;
+ let filteredLogs = executionLogList;
+ if (keyword) {
+ filteredLogs = filteredLogs.filter((log) =>
+ log.taskName.includes(keyword)
+ );
+ }
+ if (status && status.length > 0) {
+ filteredLogs = filteredLogs.filter((log) => status.includes(log.status));
+ }
+ const startIndex = (page - 1) * size;
+ const endIndex = startIndex + size;
+ const paginatedLogs = filteredLogs.slice(startIndex, endIndex);
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ totalElements: filteredLogs.length,
+ page,
+ size,
+ results: paginatedLogs,
+ },
+ });
+ });
+
+ // 获取任务执行日志详情
+ router.post(API.queryExecutionLogByIdUsingGet, (req, res) => {
+ const { id } = req.body;
+ const log = executionLogList.find((log) => log.id === id);
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: log,
+ });
+ });
+};
diff --git a/frontend/src/mock/mock-seed/data-evaluation.cjs b/frontend/src/mock/mock-seed/data-evaluation.cjs
index c241de70e..0c96a4ca3 100644
--- a/frontend/src/mock/mock-seed/data-evaluation.cjs
+++ b/frontend/src/mock/mock-seed/data-evaluation.cjs
@@ -1,501 +1,501 @@
-const Mock = require("mockjs");
-const API = require("../mock-apis.cjs");
-
-// 质量指标枚举
-const QualityMetrics = [
- "COMPLETENESS",
- "ACCURACY",
- "CONSISTENCY",
- "VALIDITY",
- "UNIQUENESS",
- "TIMELINESS"
-];
-
-// 适配性标准枚举
-const CompatibilityCriteria = [
- "FORMAT_COMPATIBILITY",
- "SCHEMA_COMPATIBILITY",
- "SIZE_ADEQUACY",
- "DISTRIBUTION_MATCH",
- "FEATURE_COVERAGE"
-];
-
-// 价值标准枚举
-const ValueCriteria = [
- "RARITY",
- "DEMAND",
- "QUALITY",
- "COMPLETENESS",
- "TIMELINESS",
- "STRATEGIC_IMPORTANCE"
-];
-
-// 评估类型枚举
-const EvaluationTypes = ["QUALITY", "COMPATIBILITY", "VALUE", "COMPREHENSIVE"];
-
-// 评估状态枚举
-const EvaluationStatuses = ["PENDING", "RUNNING", "COMPLETED", "FAILED"];
-
-// 生成质量评估结果
-function qualityEvaluationItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- status: Mock.Random.pick(EvaluationStatuses),
- overallScore: Mock.Random.float(0.6, 1.0, 2, 2),
- metrics: Mock.Random.shuffle(QualityMetrics).slice(0, Mock.Random.integer(3, 5)).map(metric => ({
- metric,
- score: Mock.Random.float(0.5, 1.0, 2, 2),
- details: {
- totalRecords: Mock.Random.integer(1000, 100000),
- validRecords: Mock.Random.integer(800, 95000),
- issues: Mock.Random.integer(0, 50)
- },
- issues: new Array(Mock.Random.integer(0, 3)).fill(null).map(() => ({
- type: Mock.Random.pick(["MISSING_VALUE", "INVALID_FORMAT", "DUPLICATE", "OUTLIER"]),
- severity: Mock.Random.pick(["LOW", "MEDIUM", "HIGH", "CRITICAL"]),
- description: Mock.Random.csentence(5, 15),
- affectedRecords: Mock.Random.integer(1, 1000),
- suggestions: [Mock.Random.csentence(5, 20)]
- }))
- })),
- recommendations: new Array(Mock.Random.integer(2, 5)).fill(null).map(() =>
- Mock.Random.csentence(10, 30)
- ),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- detailedResults: {
- fieldAnalysis: new Array(Mock.Random.integer(3, 8)).fill(null).map(() => ({
- fieldName: Mock.Random.word(5, 10),
- dataType: Mock.Random.pick(["STRING", "INTEGER", "FLOAT", "BOOLEAN", "DATE"]),
- nullCount: Mock.Random.integer(0, 100),
- uniqueCount: Mock.Random.integer(100, 1000),
- statistics: {
- mean: Mock.Random.float(0, 100, 2, 2),
- median: Mock.Random.float(0, 100, 2, 2),
- stdDev: Mock.Random.float(0, 50, 2, 2)
- }
- })),
- distributionAnalysis: {
- distributions: new Array(3).fill(null).map(() => ({
- field: Mock.Random.word(5, 10),
- type: Mock.Random.pick(["NORMAL", "UNIFORM", "SKEWED"]),
- parameters: {}
- })),
- outliers: new Array(Mock.Random.integer(0, 5)).fill(null).map(() => ({
- field: Mock.Random.word(5, 10),
- value: Mock.Random.float(-100, 100, 2, 2),
- zScore: Mock.Random.float(-3, 3, 2, 2)
- })),
- patterns: [
- "数据分布较为均匀",
- "存在少量异常值",
- "部分字段相关性较强"
- ]
- },
- correlationAnalysis: {
- correlationMatrix: new Array(5).fill(null).map(() =>
- new Array(5).fill(null).map(() => Mock.Random.float(-1, 1, 2, 2))
- ),
- significantCorrelations: new Array(Mock.Random.integer(1, 3)).fill(null).map(() => ({
- field1: Mock.Random.word(5, 10),
- field2: Mock.Random.word(5, 10),
- correlation: Mock.Random.float(0.5, 1, 2, 2),
- pValue: Mock.Random.float(0, 0.05, 3, 3)
- }))
- }
- },
- visualizations: new Array(Mock.Random.integer(2, 4)).fill(null).map(() => ({
- type: Mock.Random.pick(["CHART", "GRAPH", "HISTOGRAM", "HEATMAP"]),
- title: Mock.Random.ctitle(5, 15),
- data: {
- labels: new Array(5).fill(null).map(() => Mock.Random.word(3, 8)),
- values: new Array(5).fill(null).map(() => Mock.Random.integer(0, 100))
- },
- config: {
- width: 400,
- height: 300,
- color: Mock.Random.color()
- }
- }))
- };
-}
-
-// 生成适配性评估结果
-function compatibilityEvaluationItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- targetType: Mock.Random.pick(["LANGUAGE_MODEL", "CLASSIFICATION_MODEL", "RECOMMENDATION_SYSTEM", "CUSTOM_TASK"]),
- compatibilityScore: Mock.Random.float(0.6, 1.0, 2, 2),
- results: Mock.Random.shuffle(CompatibilityCriteria).slice(0, Mock.Random.integer(3, 5)).map(criterion => ({
- criterion,
- score: Mock.Random.float(0.5, 1.0, 2, 2),
- status: Mock.Random.pick(["PASS", "WARN", "FAIL"]),
- details: Mock.Random.csentence(10, 30)
- })),
- suggestions: new Array(Mock.Random.integer(2, 4)).fill(null).map(() =>
- Mock.Random.csentence(10, 25)
- ),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss")
- };
-}
-
-// 生成价值评估结果
-function valueEvaluationItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- valueScore: Mock.Random.float(0.6, 1.0, 2, 2),
- monetaryValue: Mock.Random.float(10000, 1000000, 2, 2),
- strategicValue: Mock.Random.float(0.6, 1.0, 2, 2),
- results: Mock.Random.shuffle(ValueCriteria).slice(0, Mock.Random.integer(3, 5)).map(criterion => ({
- criterion,
- score: Mock.Random.float(0.5, 1.0, 2, 2),
- impact: Mock.Random.pick(["LOW", "MEDIUM", "HIGH"]),
- explanation: Mock.Random.csentence(10, 30)
- })),
- insights: new Array(Mock.Random.integer(3, 6)).fill(null).map(() =>
- Mock.Random.csentence(15, 40)
- ),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss")
- };
-}
-
-// 生成评估报告
-function evaluationReportItem() {
- const type = Mock.Random.pick(EvaluationTypes);
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- datasetName: Mock.Random.ctitle(5, 15),
- type,
- status: Mock.Random.pick(EvaluationStatuses),
- overallScore: Mock.Random.float(0.6, 1.0, 2, 2),
- summary: Mock.Random.csentence(20, 50),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- completedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- qualityResults: type === "QUALITY" || type === "COMPREHENSIVE" ? qualityEvaluationItem() : null,
- compatibilityResults: type === "COMPATIBILITY" || type === "COMPREHENSIVE" ? compatibilityEvaluationItem() : null,
- valueResults: type === "VALUE" || type === "COMPREHENSIVE" ? valueEvaluationItem() : null,
- attachments: new Array(Mock.Random.integer(1, 3)).fill(null).map(() => ({
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.word(5, 10) + "." + Mock.Random.pick(["pdf", "xlsx", "json"]),
- type: Mock.Random.pick(["PDF", "EXCEL", "JSON"]),
- size: Mock.Random.integer(1024, 1024 * 1024),
- downloadUrl: "/api/v1/evaluation/attachments/" + Mock.Random.guid()
- }))
- };
-}
-
-const qualityEvaluationList = new Array(30).fill(null).map(qualityEvaluationItem);
-const compatibilityEvaluationList = new Array(20).fill(null).map(compatibilityEvaluationItem);
-const valueEvaluationList = new Array(25).fill(null).map(valueEvaluationItem);
-const evaluationReportList = new Array(50).fill(null).map(evaluationReportItem);
-
-module.exports = function (router) {
- // 数据质量评估
- router.post(API.evaluateDataQualityUsingPost, (req, res) => {
- const { datasetId, metrics, sampleSize, parameters } = req.body;
-
- const newEvaluation = {
- ...qualityEvaluationItem(),
- datasetId,
- status: "RUNNING",
- metrics: metrics.map(metric => ({
- metric,
- score: Mock.Random.float(0.5, 1.0, 2, 2),
- details: {
- totalRecords: sampleSize || Mock.Random.integer(1000, 100000),
- validRecords: Mock.Random.integer(800, 95000),
- issues: Mock.Random.integer(0, 50)
- },
- issues: []
- })),
- createdAt: new Date().toISOString()
- };
-
- qualityEvaluationList.push(newEvaluation);
-
- // 模拟异步处理,2秒后完成
- setTimeout(() => {
- newEvaluation.status = "COMPLETED";
- }, 2000);
-
- res.send({
- code: "0",
- msg: "Quality evaluation started successfully",
- data: newEvaluation
- });
- });
-
- // 获取质量评估结果
- router.get(API.getQualityEvaluationByIdUsingGet, (req, res) => {
- const { evaluationId } = req.params;
- const evaluation = qualityEvaluationList.find(e => e.id === evaluationId);
-
- if (evaluation) {
- res.send({
- code: "0",
- msg: "Success",
- data: evaluation
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Quality evaluation not found",
- data: null
- });
- }
- });
-
- // 适配性评估
- router.post(API.evaluateCompatibilityUsingPost, (req, res) => {
- const { datasetId, targetType, targetConfig, evaluationCriteria } = req.body;
-
- const newEvaluation = {
- ...compatibilityEvaluationItem(),
- datasetId,
- targetType,
- results: evaluationCriteria.map(criterion => ({
- criterion,
- score: Mock.Random.float(0.5, 1.0, 2, 2),
- status: Mock.Random.pick(["PASS", "WARN", "FAIL"]),
- details: Mock.Random.csentence(10, 30)
- })),
- createdAt: new Date().toISOString()
- };
-
- compatibilityEvaluationList.push(newEvaluation);
-
- res.send({
- code: "0",
- msg: "Compatibility evaluation completed successfully",
- data: newEvaluation
- });
- });
-
- // 价值评估
- router.post(API.evaluateValueUsingPost, (req, res) => {
- const { datasetId, valueCriteria, marketContext, businessContext } = req.body;
-
- const newEvaluation = {
- ...valueEvaluationItem(),
- datasetId,
- results: valueCriteria.map(criterion => ({
- criterion,
- score: Mock.Random.float(0.5, 1.0, 2, 2),
- impact: Mock.Random.pick(["LOW", "MEDIUM", "HIGH"]),
- explanation: Mock.Random.csentence(10, 30)
- })),
- createdAt: new Date().toISOString()
- };
-
- valueEvaluationList.push(newEvaluation);
-
- res.send({
- code: "0",
- msg: "Value evaluation completed successfully",
- data: newEvaluation
- });
- });
-
- // 获取评估报告列表
- router.get(API.queryEvaluationReportsUsingGet, (req, res) => {
- const { page = 0, size = 20, type, datasetId } = req.query;
- let filteredReports = evaluationReportList;
-
- if (type) {
- filteredReports = filteredReports.filter(report => report.type === type);
- }
-
- if (datasetId) {
- filteredReports = filteredReports.filter(report => report.datasetId === datasetId);
- }
-
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = filteredReports.slice(startIndex, endIndex);
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- content: pageData,
- totalElements: filteredReports.length,
- totalPages: Math.ceil(filteredReports.length / size),
- size: parseInt(size),
- number: parseInt(page)
- }
- });
- });
-
- // 获取评估报告详情
- router.get(API.getEvaluationReportByIdUsingGet, (req, res) => {
- const { reportId } = req.params;
- const report = evaluationReportList.find(r => r.id === reportId);
-
- if (report) {
- res.send({
- code: "0",
- msg: "Success",
- data: report
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Evaluation report not found",
- data: null
- });
- }
- });
-
- // 导出评估报告
- router.get(API.exportEvaluationReportUsingGet, (req, res) => {
- const { reportId } = req.params;
- const { format = "PDF" } = req.query;
- const report = evaluationReportList.find(r => r.id === reportId);
-
- if (report) {
- const fileName = `evaluation_report_${reportId}.${format.toLowerCase()}`;
-
- res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
- res.setHeader('Content-Type', 'application/octet-stream');
- res.send(`Mock ${format} content for evaluation report ${reportId}`);
- } else {
- res.status(404).send({
- code: "1",
- msg: "Evaluation report not found",
- data: null
- });
- }
- });
-
- // 批量评估
- router.post(API.batchEvaluationUsingPost, (req, res) => {
- const { datasetIds, evaluationTypes, parameters } = req.body;
-
- const batchId = Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, "");
- const totalTasks = datasetIds.length * evaluationTypes.length;
-
- // 为每个数据集和评估类型组合创建任务
- datasetIds.forEach(datasetId => {
- evaluationTypes.forEach(type => {
- const report = {
- ...evaluationReportItem(),
- datasetId,
- type,
- status: "PENDING",
- batchId
- };
- evaluationReportList.push(report);
-
- // 模拟异步处理
- setTimeout(() => {
- report.status = "COMPLETED";
- }, Mock.Random.integer(3000, 10000));
- });
- });
-
- res.status(202).send({
- code: "0",
- msg: "Batch evaluation submitted successfully",
- data: {
- batchId,
- status: "SUBMITTED",
- totalTasks,
- submittedAt: new Date().toISOString()
- }
- });
- });
-
- // 获取批量评估状态
- router.get("/api/v1/evaluation/batch/:batchId", (req, res) => {
- const { batchId } = req.params;
- const batchReports = evaluationReportList.filter(r => r.batchId === batchId);
-
- const completedTasks = batchReports.filter(r => r.status === "COMPLETED").length;
- const runningTasks = batchReports.filter(r => r.status === "RUNNING").length;
- const pendingTasks = batchReports.filter(r => r.status === "PENDING").length;
- const failedTasks = batchReports.filter(r => r.status === "FAILED").length;
-
- let overallStatus = "COMPLETED";
- if (runningTasks > 0 || pendingTasks > 0) {
- overallStatus = "RUNNING";
- } else if (failedTasks > 0) {
- overallStatus = "PARTIAL_FAILED";
- }
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- batchId,
- status: overallStatus,
- totalTasks: batchReports.length,
- completedTasks,
- runningTasks,
- pendingTasks,
- failedTasks,
- progress: batchReports.length > 0 ? Math.round((completedTasks / batchReports.length) * 100) : 0,
- reports: batchReports
- }
- });
- });
-
- // 获取评估统计信息
- router.get("/api/v1/evaluation/statistics", (req, res) => {
- const { timeRange = "LAST_30_DAYS" } = req.query;
-
- const statistics = {
- totalEvaluations: evaluationReportList.length,
- completedEvaluations: evaluationReportList.filter(r => r.status === "COMPLETED").length,
- runningEvaluations: evaluationReportList.filter(r => r.status === "RUNNING").length,
- failedEvaluations: evaluationReportList.filter(r => r.status === "FAILED").length,
- averageScore: Mock.Random.float(0.75, 0.95, 2, 2),
- evaluationTypeDistribution: {
- QUALITY: evaluationReportList.filter(r => r.type === "QUALITY").length,
- COMPATIBILITY: evaluationReportList.filter(r => r.type === "COMPATIBILITY").length,
- VALUE: evaluationReportList.filter(r => r.type === "VALUE").length,
- COMPREHENSIVE: evaluationReportList.filter(r => r.type === "COMPREHENSIVE").length
- },
- scoreDistribution: {
- excellent: evaluationReportList.filter(r => r.overallScore >= 0.9).length,
- good: evaluationReportList.filter(r => r.overallScore >= 0.8 && r.overallScore < 0.9).length,
- fair: evaluationReportList.filter(r => r.overallScore >= 0.6 && r.overallScore < 0.8).length,
- poor: evaluationReportList.filter(r => r.overallScore < 0.6).length
- },
- trends: new Array(30).fill(null).map((_, index) => ({
- date: Mock.Random.date("yyyy-MM-dd"),
- evaluations: Mock.Random.integer(5, 50),
- averageScore: Mock.Random.float(0.7, 0.95, 2, 2)
- }))
- };
-
- res.send({
- code: "0",
- msg: "Success",
- data: statistics
- });
- });
-
- // 删除评估报告
- router.delete("/api/v1/evaluation/reports/:reportId", (req, res) => {
- const { reportId } = req.params;
- const index = evaluationReportList.findIndex(r => r.id === reportId);
-
- if (index !== -1) {
- evaluationReportList.splice(index, 1);
- res.send({
- code: "0",
- msg: "Evaluation report deleted successfully",
- data: null
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Evaluation report not found",
- data: null
- });
- }
- });
+const Mock = require("mockjs");
+const API = require("../mock-apis.cjs");
+
+// 质量指标枚举
+const QualityMetrics = [
+ "COMPLETENESS",
+ "ACCURACY",
+ "CONSISTENCY",
+ "VALIDITY",
+ "UNIQUENESS",
+ "TIMELINESS"
+];
+
+// 适配性标准枚举
+const CompatibilityCriteria = [
+ "FORMAT_COMPATIBILITY",
+ "SCHEMA_COMPATIBILITY",
+ "SIZE_ADEQUACY",
+ "DISTRIBUTION_MATCH",
+ "FEATURE_COVERAGE"
+];
+
+// 价值标准枚举
+const ValueCriteria = [
+ "RARITY",
+ "DEMAND",
+ "QUALITY",
+ "COMPLETENESS",
+ "TIMELINESS",
+ "STRATEGIC_IMPORTANCE"
+];
+
+// 评估类型枚举
+const EvaluationTypes = ["QUALITY", "COMPATIBILITY", "VALUE", "COMPREHENSIVE"];
+
+// 评估状态枚举
+const EvaluationStatuses = ["PENDING", "RUNNING", "COMPLETED", "FAILED"];
+
+// 生成质量评估结果
+function qualityEvaluationItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ status: Mock.Random.pick(EvaluationStatuses),
+ overallScore: Mock.Random.float(0.6, 1.0, 2, 2),
+ metrics: Mock.Random.shuffle(QualityMetrics).slice(0, Mock.Random.integer(3, 5)).map(metric => ({
+ metric,
+ score: Mock.Random.float(0.5, 1.0, 2, 2),
+ details: {
+ totalRecords: Mock.Random.integer(1000, 100000),
+ validRecords: Mock.Random.integer(800, 95000),
+ issues: Mock.Random.integer(0, 50)
+ },
+ issues: new Array(Mock.Random.integer(0, 3)).fill(null).map(() => ({
+ type: Mock.Random.pick(["MISSING_VALUE", "INVALID_FORMAT", "DUPLICATE", "OUTLIER"]),
+ severity: Mock.Random.pick(["LOW", "MEDIUM", "HIGH", "CRITICAL"]),
+ description: Mock.Random.csentence(5, 15),
+ affectedRecords: Mock.Random.integer(1, 1000),
+ suggestions: [Mock.Random.csentence(5, 20)]
+ }))
+ })),
+ recommendations: new Array(Mock.Random.integer(2, 5)).fill(null).map(() =>
+ Mock.Random.csentence(10, 30)
+ ),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ detailedResults: {
+ fieldAnalysis: new Array(Mock.Random.integer(3, 8)).fill(null).map(() => ({
+ fieldName: Mock.Random.word(5, 10),
+ dataType: Mock.Random.pick(["STRING", "INTEGER", "FLOAT", "BOOLEAN", "DATE"]),
+ nullCount: Mock.Random.integer(0, 100),
+ uniqueCount: Mock.Random.integer(100, 1000),
+ statistics: {
+ mean: Mock.Random.float(0, 100, 2, 2),
+ median: Mock.Random.float(0, 100, 2, 2),
+ stdDev: Mock.Random.float(0, 50, 2, 2)
+ }
+ })),
+ distributionAnalysis: {
+ distributions: new Array(3).fill(null).map(() => ({
+ field: Mock.Random.word(5, 10),
+ type: Mock.Random.pick(["NORMAL", "UNIFORM", "SKEWED"]),
+ parameters: {}
+ })),
+ outliers: new Array(Mock.Random.integer(0, 5)).fill(null).map(() => ({
+ field: Mock.Random.word(5, 10),
+ value: Mock.Random.float(-100, 100, 2, 2),
+ zScore: Mock.Random.float(-3, 3, 2, 2)
+ })),
+ patterns: [
+ "数据分布较为均匀",
+ "存在少量异常值",
+ "部分字段相关性较强"
+ ]
+ },
+ correlationAnalysis: {
+ correlationMatrix: new Array(5).fill(null).map(() =>
+ new Array(5).fill(null).map(() => Mock.Random.float(-1, 1, 2, 2))
+ ),
+ significantCorrelations: new Array(Mock.Random.integer(1, 3)).fill(null).map(() => ({
+ field1: Mock.Random.word(5, 10),
+ field2: Mock.Random.word(5, 10),
+ correlation: Mock.Random.float(0.5, 1, 2, 2),
+ pValue: Mock.Random.float(0, 0.05, 3, 3)
+ }))
+ }
+ },
+ visualizations: new Array(Mock.Random.integer(2, 4)).fill(null).map(() => ({
+ type: Mock.Random.pick(["CHART", "GRAPH", "HISTOGRAM", "HEATMAP"]),
+ title: Mock.Random.ctitle(5, 15),
+ data: {
+ labels: new Array(5).fill(null).map(() => Mock.Random.word(3, 8)),
+ values: new Array(5).fill(null).map(() => Mock.Random.integer(0, 100))
+ },
+ config: {
+ width: 400,
+ height: 300,
+ color: Mock.Random.color()
+ }
+ }))
+ };
+}
+
+// 生成适配性评估结果
+function compatibilityEvaluationItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ targetType: Mock.Random.pick(["LANGUAGE_MODEL", "CLASSIFICATION_MODEL", "RECOMMENDATION_SYSTEM", "CUSTOM_TASK"]),
+ compatibilityScore: Mock.Random.float(0.6, 1.0, 2, 2),
+ results: Mock.Random.shuffle(CompatibilityCriteria).slice(0, Mock.Random.integer(3, 5)).map(criterion => ({
+ criterion,
+ score: Mock.Random.float(0.5, 1.0, 2, 2),
+ status: Mock.Random.pick(["PASS", "WARN", "FAIL"]),
+ details: Mock.Random.csentence(10, 30)
+ })),
+ suggestions: new Array(Mock.Random.integer(2, 4)).fill(null).map(() =>
+ Mock.Random.csentence(10, 25)
+ ),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss")
+ };
+}
+
+// 生成价值评估结果
+function valueEvaluationItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ valueScore: Mock.Random.float(0.6, 1.0, 2, 2),
+ monetaryValue: Mock.Random.float(10000, 1000000, 2, 2),
+ strategicValue: Mock.Random.float(0.6, 1.0, 2, 2),
+ results: Mock.Random.shuffle(ValueCriteria).slice(0, Mock.Random.integer(3, 5)).map(criterion => ({
+ criterion,
+ score: Mock.Random.float(0.5, 1.0, 2, 2),
+ impact: Mock.Random.pick(["LOW", "MEDIUM", "HIGH"]),
+ explanation: Mock.Random.csentence(10, 30)
+ })),
+ insights: new Array(Mock.Random.integer(3, 6)).fill(null).map(() =>
+ Mock.Random.csentence(15, 40)
+ ),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss")
+ };
+}
+
+// 生成评估报告
+function evaluationReportItem() {
+ const type = Mock.Random.pick(EvaluationTypes);
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ datasetName: Mock.Random.ctitle(5, 15),
+ type,
+ status: Mock.Random.pick(EvaluationStatuses),
+ overallScore: Mock.Random.float(0.6, 1.0, 2, 2),
+ summary: Mock.Random.csentence(20, 50),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ completedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ qualityResults: type === "QUALITY" || type === "COMPREHENSIVE" ? qualityEvaluationItem() : null,
+ compatibilityResults: type === "COMPATIBILITY" || type === "COMPREHENSIVE" ? compatibilityEvaluationItem() : null,
+ valueResults: type === "VALUE" || type === "COMPREHENSIVE" ? valueEvaluationItem() : null,
+ attachments: new Array(Mock.Random.integer(1, 3)).fill(null).map(() => ({
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.word(5, 10) + "." + Mock.Random.pick(["pdf", "xlsx", "json"]),
+ type: Mock.Random.pick(["PDF", "EXCEL", "JSON"]),
+ size: Mock.Random.integer(1024, 1024 * 1024),
+ downloadUrl: "/api/v1/evaluation/attachments/" + Mock.Random.guid()
+ }))
+ };
+}
+
+const qualityEvaluationList = new Array(30).fill(null).map(qualityEvaluationItem);
+const compatibilityEvaluationList = new Array(20).fill(null).map(compatibilityEvaluationItem);
+const valueEvaluationList = new Array(25).fill(null).map(valueEvaluationItem);
+const evaluationReportList = new Array(50).fill(null).map(evaluationReportItem);
+
+module.exports = function (router) {
+ // 数据质量评估
+ router.post(API.evaluateDataQualityUsingPost, (req, res) => {
+ const { datasetId, metrics, sampleSize, parameters } = req.body;
+
+ const newEvaluation = {
+ ...qualityEvaluationItem(),
+ datasetId,
+ status: "RUNNING",
+ metrics: metrics.map(metric => ({
+ metric,
+ score: Mock.Random.float(0.5, 1.0, 2, 2),
+ details: {
+ totalRecords: sampleSize || Mock.Random.integer(1000, 100000),
+ validRecords: Mock.Random.integer(800, 95000),
+ issues: Mock.Random.integer(0, 50)
+ },
+ issues: []
+ })),
+ createdAt: new Date().toISOString()
+ };
+
+ qualityEvaluationList.push(newEvaluation);
+
+ // 模拟异步处理,2秒后完成
+ setTimeout(() => {
+ newEvaluation.status = "COMPLETED";
+ }, 2000);
+
+ res.send({
+ code: "0",
+ msg: "Quality evaluation started successfully",
+ data: newEvaluation
+ });
+ });
+
+ // 获取质量评估结果
+ router.get(API.getQualityEvaluationByIdUsingGet, (req, res) => {
+ const { evaluationId } = req.params;
+ const evaluation = qualityEvaluationList.find(e => e.id === evaluationId);
+
+ if (evaluation) {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: evaluation
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Quality evaluation not found",
+ data: null
+ });
+ }
+ });
+
+ // 适配性评估
+ router.post(API.evaluateCompatibilityUsingPost, (req, res) => {
+ const { datasetId, targetType, targetConfig, evaluationCriteria } = req.body;
+
+ const newEvaluation = {
+ ...compatibilityEvaluationItem(),
+ datasetId,
+ targetType,
+ results: evaluationCriteria.map(criterion => ({
+ criterion,
+ score: Mock.Random.float(0.5, 1.0, 2, 2),
+ status: Mock.Random.pick(["PASS", "WARN", "FAIL"]),
+ details: Mock.Random.csentence(10, 30)
+ })),
+ createdAt: new Date().toISOString()
+ };
+
+ compatibilityEvaluationList.push(newEvaluation);
+
+ res.send({
+ code: "0",
+ msg: "Compatibility evaluation completed successfully",
+ data: newEvaluation
+ });
+ });
+
+ // 价值评估
+ router.post(API.evaluateValueUsingPost, (req, res) => {
+ const { datasetId, valueCriteria, marketContext, businessContext } = req.body;
+
+ const newEvaluation = {
+ ...valueEvaluationItem(),
+ datasetId,
+ results: valueCriteria.map(criterion => ({
+ criterion,
+ score: Mock.Random.float(0.5, 1.0, 2, 2),
+ impact: Mock.Random.pick(["LOW", "MEDIUM", "HIGH"]),
+ explanation: Mock.Random.csentence(10, 30)
+ })),
+ createdAt: new Date().toISOString()
+ };
+
+ valueEvaluationList.push(newEvaluation);
+
+ res.send({
+ code: "0",
+ msg: "Value evaluation completed successfully",
+ data: newEvaluation
+ });
+ });
+
+ // 获取评估报告列表
+ router.get(API.queryEvaluationReportsUsingGet, (req, res) => {
+ const { page = 0, size = 20, type, datasetId } = req.query;
+ let filteredReports = evaluationReportList;
+
+ if (type) {
+ filteredReports = filteredReports.filter(report => report.type === type);
+ }
+
+ if (datasetId) {
+ filteredReports = filteredReports.filter(report => report.datasetId === datasetId);
+ }
+
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = filteredReports.slice(startIndex, endIndex);
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: pageData,
+ totalElements: filteredReports.length,
+ totalPages: Math.ceil(filteredReports.length / size),
+ size: parseInt(size),
+ number: parseInt(page)
+ }
+ });
+ });
+
+ // 获取评估报告详情
+ router.get(API.getEvaluationReportByIdUsingGet, (req, res) => {
+ const { reportId } = req.params;
+ const report = evaluationReportList.find(r => r.id === reportId);
+
+ if (report) {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: report
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Evaluation report not found",
+ data: null
+ });
+ }
+ });
+
+ // 导出评估报告
+ router.get(API.exportEvaluationReportUsingGet, (req, res) => {
+ const { reportId } = req.params;
+ const { format = "PDF" } = req.query;
+ const report = evaluationReportList.find(r => r.id === reportId);
+
+ if (report) {
+ const fileName = `evaluation_report_${reportId}.${format.toLowerCase()}`;
+
+ res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
+ res.setHeader('Content-Type', 'application/octet-stream');
+ res.send(`Mock ${format} content for evaluation report ${reportId}`);
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Evaluation report not found",
+ data: null
+ });
+ }
+ });
+
+ // 批量评估
+ router.post(API.batchEvaluationUsingPost, (req, res) => {
+ const { datasetIds, evaluationTypes, parameters } = req.body;
+
+ const batchId = Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, "");
+ const totalTasks = datasetIds.length * evaluationTypes.length;
+
+ // 为每个数据集和评估类型组合创建任务
+ datasetIds.forEach(datasetId => {
+ evaluationTypes.forEach(type => {
+ const report = {
+ ...evaluationReportItem(),
+ datasetId,
+ type,
+ status: "PENDING",
+ batchId
+ };
+ evaluationReportList.push(report);
+
+ // 模拟异步处理
+ setTimeout(() => {
+ report.status = "COMPLETED";
+ }, Mock.Random.integer(3000, 10000));
+ });
+ });
+
+ res.status(202).send({
+ code: "0",
+ msg: "Batch evaluation submitted successfully",
+ data: {
+ batchId,
+ status: "SUBMITTED",
+ totalTasks,
+ submittedAt: new Date().toISOString()
+ }
+ });
+ });
+
+ // 获取批量评估状态
+ router.get("/api/v1/evaluation/batch/:batchId", (req, res) => {
+ const { batchId } = req.params;
+ const batchReports = evaluationReportList.filter(r => r.batchId === batchId);
+
+ const completedTasks = batchReports.filter(r => r.status === "COMPLETED").length;
+ const runningTasks = batchReports.filter(r => r.status === "RUNNING").length;
+ const pendingTasks = batchReports.filter(r => r.status === "PENDING").length;
+ const failedTasks = batchReports.filter(r => r.status === "FAILED").length;
+
+ let overallStatus = "COMPLETED";
+ if (runningTasks > 0 || pendingTasks > 0) {
+ overallStatus = "RUNNING";
+ } else if (failedTasks > 0) {
+ overallStatus = "PARTIAL_FAILED";
+ }
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ batchId,
+ status: overallStatus,
+ totalTasks: batchReports.length,
+ completedTasks,
+ runningTasks,
+ pendingTasks,
+ failedTasks,
+ progress: batchReports.length > 0 ? Math.round((completedTasks / batchReports.length) * 100) : 0,
+ reports: batchReports
+ }
+ });
+ });
+
+ // 获取评估统计信息
+ router.get("/api/v1/evaluation/statistics", (req, res) => {
+ const { timeRange = "LAST_30_DAYS" } = req.query;
+
+ const statistics = {
+ totalEvaluations: evaluationReportList.length,
+ completedEvaluations: evaluationReportList.filter(r => r.status === "COMPLETED").length,
+ runningEvaluations: evaluationReportList.filter(r => r.status === "RUNNING").length,
+ failedEvaluations: evaluationReportList.filter(r => r.status === "FAILED").length,
+ averageScore: Mock.Random.float(0.75, 0.95, 2, 2),
+ evaluationTypeDistribution: {
+ QUALITY: evaluationReportList.filter(r => r.type === "QUALITY").length,
+ COMPATIBILITY: evaluationReportList.filter(r => r.type === "COMPATIBILITY").length,
+ VALUE: evaluationReportList.filter(r => r.type === "VALUE").length,
+ COMPREHENSIVE: evaluationReportList.filter(r => r.type === "COMPREHENSIVE").length
+ },
+ scoreDistribution: {
+ excellent: evaluationReportList.filter(r => r.overallScore >= 0.9).length,
+ good: evaluationReportList.filter(r => r.overallScore >= 0.8 && r.overallScore < 0.9).length,
+ fair: evaluationReportList.filter(r => r.overallScore >= 0.6 && r.overallScore < 0.8).length,
+ poor: evaluationReportList.filter(r => r.overallScore < 0.6).length
+ },
+ trends: new Array(30).fill(null).map((_, index) => ({
+ date: Mock.Random.date("yyyy-MM-dd"),
+ evaluations: Mock.Random.integer(5, 50),
+ averageScore: Mock.Random.float(0.7, 0.95, 2, 2)
+ }))
+ };
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: statistics
+ });
+ });
+
+ // 删除评估报告
+ router.delete("/api/v1/evaluation/reports/:reportId", (req, res) => {
+ const { reportId } = req.params;
+ const index = evaluationReportList.findIndex(r => r.id === reportId);
+
+ if (index !== -1) {
+ evaluationReportList.splice(index, 1);
+ res.send({
+ code: "0",
+ msg: "Evaluation report deleted successfully",
+ data: null
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Evaluation report not found",
+ data: null
+ });
+ }
+ });
};
\ No newline at end of file
diff --git a/frontend/src/mock/mock-seed/data-management.cjs b/frontend/src/mock/mock-seed/data-management.cjs
index b1702cef3..aa9f9531b 100644
--- a/frontend/src/mock/mock-seed/data-management.cjs
+++ b/frontend/src/mock/mock-seed/data-management.cjs
@@ -1,435 +1,435 @@
-const Mock = require("mockjs");
-const API = require("../mock-apis.cjs");
-
-function tagItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.word(3, 10),
- description: Mock.Random.csentence(5, 20),
- color: Mock.Random.color(),
- usageCount: Mock.Random.integer(0, 100),
- };
-}
-const tagList = new Array(20).fill(null).map((_, index) => tagItem(index));
-
-function datasetItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.ctitle(5, 20),
- datasetType: Mock.Random.pick(["TEXT", "IMAGE", "AUDIO", "VIDEO"]),
- status: Mock.Random.pick(["DRAFT","ACTIVE", "INACTIVE", "PROCESSING"]),
- tags: Mock.Random.shuffle(tagList).slice(0, Mock.Random.integer(1, 3)),
- totalSize: Mock.Random.integer(1024, 1024 * 1024 * 1024), // in bytes
- description: Mock.Random.cparagraph(1, 3),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- createdBy: Mock.Random.cname(),
- updatedBy: Mock.Random.cname(),
- };
-}
-
-const datasetList = new Array(50)
- .fill(null)
- .map((_, index) => datasetItem(index));
-
-function datasetFileItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- fileName:
- Mock.Random.word(5, 15) +
- "." +
- Mock.Random.pick(["csv", "json", "xml", "parquet", "avro"]),
- originName:
- Mock.Random.word(5, 15) +
- "." +
- Mock.Random.pick(["csv", "json", "xml", "parquet", "avro"]),
- fileType: Mock.Random.pick(["CSV", "JSON", "XML", "Parquet", "Avro"]),
- size: Mock.Random.integer(1024, 1024 * 1024 * 1024), // in bytes
- type: Mock.Random.pick(["CSV", "JSON", "XML", "Parquet", "Avro"]),
- status: Mock.Random.pick(["UPLOADED", "PROCESSING", "COMPLETED", "ERROR"]),
- description: Mock.Random.csentence(5, 20),
- filePath: "/path/to/file/" + Mock.Random.word(5, 10),
- uploadedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- uploadedBy: Mock.Random.cname(),
- };
-}
-
-const datasetFileList = new Array(200)
- .fill(null)
- .map((_, index) => datasetFileItem(index));
-
-const datasetStatistics = {
- count: {
- text: 10,
- image: 34,
- audio: 23,
- video: 5,
- },
- size: {
- text: "120 MB",
- image: "3.4 GB",
- audio: "2.3 GB",
- video: "15 GB",
- },
- totalDatasets: datasetList.length,
- totalFiles: datasetFileList.length,
- completedFiles: datasetFileList.filter((file) => file.status === "COMPLETED")
- .length,
- totalSize: datasetFileList.reduce((acc, file) => acc + file.size, 0), // in bytes
- completionRate:
- datasetFileList.length === 0
- ? 0
- : Math.round(
- (datasetFileList.filter((file) => file.status === "COMPLETED")
- .length /
- datasetFileList.length) *
- 100
- ), // percentage
-};
-
-const datasetTypes = [
- {
- code: "PRETRAIN",
- name: "预训练数据集",
- description: "用于模型预训练的大规模数据集",
- supportedFormats: ["txt", "json", "csv", "parquet"],
- icon: "brain",
- },
- {
- code: "FINE_TUNE",
- name: "微调数据集",
- description: "用于模型微调的专业数据集",
- supportedFormats: ["json", "csv", "xlsx"],
- icon: "tune",
- },
- {
- code: "EVAL",
- name: "评估数据集",
- description: "用于模型评估的标准数据集",
- supportedFormats: ["json", "csv", "xml"],
- icon: "assessment",
- },
-];
-
-module.exports = { datasetList };
-module.exports = function (router) {
- // 获取数据统计信息
- router.get(API.queryDatasetStatisticsUsingGet, (req, res) => {
- res.send({
- code: "0",
- msg: "Success",
- data: datasetStatistics,
- });
- });
-
- // 创建数据
- router.post(API.createDatasetUsingPost, (req, res) => {
- const newDataset = {
- ...req.body,
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- status: "ACTIVE",
- fileCount: 0,
- totalSize: 0,
- completionRate: 0,
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- createdBy: "Admin",
- updatedBy: "Admin",
- tags: tagList.filter((tag) => req.body?.tagIds?.includes?.(tag.id)),
- };
- datasetList.unshift(newDataset); // Add to the beginning of the list
- res.send({
- code: "0",
- msg: "Dataset created successfully",
- data: newDataset,
- });
- });
-
- // 获取数据集列表
- router.get(API.queryDatasetsUsingGet, (req, res) => {
- const { page = 0, size = 10, keyword, type, status, tags } = req.query;
- console.log("Received query params:", req.query);
-
- let filteredDatasets = datasetList;
- if (keyword) {
- console.log("filter keyword:", keyword);
-
- filteredDatasets = filteredDatasets.filter(
- (dataset) =>
- dataset.name.includes(keyword) ||
- dataset.description.includes(keyword)
- );
- }
- if (type) {
- filteredDatasets = filteredDatasets.filter(
- (dataset) => dataset.datasetType === type
- );
- }
- if (status) {
- console.log("filter status:", status);
- filteredDatasets = filteredDatasets.filter(
- (dataset) => dataset.status === status
- );
- }
- if (tags && tags.length > 0) {
- console.log("filter tags:", tags);
- filteredDatasets = filteredDatasets.filter((dataset) =>
- tags.every((tag) => dataset.tags.some((t) => t.name === tag))
- );
- }
-
- const totalElements = filteredDatasets.length;
- const paginatedDatasets = filteredDatasets.slice(
- page * size,
- (page + 1) * size
- );
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- totalElements,
- page,
- size,
- content: paginatedDatasets,
- },
- });
- });
-
- // 根据ID获取数据集详情
- router.get(API.queryDatasetByIdUsingGet, (req, res) => {
- const { id } = req.params;
-
- const dataset = datasetList.find((d) => d.id === id);
- if (dataset) {
- res.send({
- code: "0",
- msg: "Success",
- data: dataset,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Dataset not found",
- data: null,
- });
- }
- });
-
- // 更新数据集
- router.put(API.updateDatasetByIdUsingPut, (req, res) => {
- const { id } = req.params;
- let { tags } = req.body;
-
- const index = datasetList.findIndex((d) => d.id === id);
- tags = [...datasetList[index].tags.map((tag) => tag.name), ...tags];
- if (index !== -1) {
- datasetList[index] = {
- ...datasetList[index],
- ...req.body,
- tags: tagList.filter((tag) => tags?.includes?.(tag.name)),
- updatedAt: new Date().toISOString(),
- updatedBy: "Admin",
- };
- res.send({
- code: "0",
- msg: "Dataset updated successfully",
- data: datasetList[index],
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Dataset not found",
- data: null,
- });
- }
- });
-
- // 删除数据集
- router.delete(API.deleteDatasetByIdUsingDelete, (req, res) => {
- const { datasetId } = req.params;
- const index = datasetList.findIndex((d) => d.id === datasetId);
-
- if (index !== -1) {
- datasetList.splice(index, 1);
- res.status(204).send();
- } else {
- res.status(404).send({
- code: "1",
- msg: "Dataset not found",
- data: null,
- });
- }
- });
-
- // 获取数据集文件列表
- router.get(API.queryFilesUsingGet, (req, res) => {
- const { datasetId } = req.params;
- const { page = 0, size = 20, fileType, status } = req.query;
-
- let filteredFiles = datasetFileList;
-
- if (fileType) {
- filteredFiles = filteredFiles.filter(
- (file) => file.fileType === fileType
- );
- }
-
- if (status) {
- filteredFiles = filteredFiles.filter((file) => file.status === status);
- }
-
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = filteredFiles.slice(startIndex, endIndex);
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- content: pageData,
- page: parseInt(page),
- size: parseInt(size),
- totalElements: filteredFiles.length,
- },
- });
- });
-
- // 上传文件到数据集
- router.post(API.uploadFileUsingPost, (req, res) => {
- const { datasetId } = req.params;
- const newFile = {
- ...datasetFileItem(),
- ...req.body,
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- uploadedAt: new Date().toISOString(),
- uploadedBy: "Admin",
- };
-
- datasetFileList.push(newFile);
-
- res.status(201).send({
- code: "0",
- msg: "File uploaded successfully",
- data: newFile,
- });
- });
-
- // 获取文件详情
- router.get(API.queryFileByIdUsingGet, (req, res) => {
- const { datasetId, fileId } = req.params;
- const file = datasetFileList.find((f) => f.id === fileId);
-
- if (file) {
- res.send({
- code: "0",
- msg: "Success",
- data: file,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "File not found",
- data: null,
- });
- }
- });
-
- // 删除文件
- router.delete(API.deleteFileByIdUsingDelete, (req, res) => {
- const { datasetId, fileId } = req.params;
- const index = datasetFileList.findIndex((f) => f.id === fileId);
-
- if (index !== -1) {
- datasetFileList.splice(index, 1);
- res.status(204).send();
- } else {
- res.status(404).send({
- code: "1",
- msg: "File not found",
- data: null,
- });
- }
- });
-
- // 下载文件
- router.get(API.downloadFileByIdUsingGet, (req, res) => {
- const { datasetId, fileId } = req.params;
- const file = datasetFileList.find((f) => f.id === fileId);
-
- if (file) {
- res.setHeader(
- "Content-Disposition",
- `attachment; filename="${file.fileName}"`
- );
- res.setHeader("Content-Type", "application/octet-stream");
- res.send(`Mock file content for ${file.fileName}`);
- } else {
- res.status(404).send({
- code: "1",
- msg: "File not found",
- data: null,
- });
- }
- });
-
- // 获取数据集类型列表
- router.get(API.queryDatasetTypesUsingGet, (req, res) => {
- res.send({
- code: "0",
- msg: "Success",
- data: datasetTypes,
- });
- });
-
- // 获取标签列表
- router.get(API.queryTagsUsingGet, (req, res) => {
- const { keyword } = req.query;
- let filteredTags = tagList;
-
- if (keyword) {
- filteredTags = tagList.filter((tag) =>
- tag.name.toLowerCase().includes(keyword.toLowerCase())
- );
- }
-
- res.send({
- code: "0",
- msg: "Success",
- data: filteredTags,
- });
- });
-
- // 创建标签
- router.post(API.createTagUsingPost, (req, res) => {
- const newTag = {
- ...tagItem(),
- ...req.body,
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- usageCount: 0,
- };
-
- tagList.push(newTag);
-
- res.status(201).send({
- code: "0",
- msg: "Tag created successfully",
- data: newTag,
- });
- });
-
- router.post(API.preUploadFileUsingPost, (req, res) => {
- res.status(201).send(Mock.Random.guid());
- });
-
- // 上传
- router.post(API.uploadFileChunkUsingPost, (req, res) => {
- res.status(500).send({ message: "Simulated upload failure" });
- // res.status(201).send({ data: "success" });
- });
-
- // 取消上传
- router.put(API.cancelUploadUsingPut, (req, res) => {
- res.status(201).send({ data: "success" });
- });
-};
+const Mock = require("mockjs");
+const API = require("../mock-apis.cjs");
+
+function tagItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.word(3, 10),
+ description: Mock.Random.csentence(5, 20),
+ color: Mock.Random.color(),
+ usageCount: Mock.Random.integer(0, 100),
+ };
+}
+const tagList = new Array(20).fill(null).map((_, index) => tagItem(index));
+
+function datasetItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.ctitle(5, 20),
+ datasetType: Mock.Random.pick(["TEXT", "IMAGE", "AUDIO", "VIDEO"]),
+ status: Mock.Random.pick(["DRAFT","ACTIVE", "INACTIVE", "PROCESSING"]),
+ tags: Mock.Random.shuffle(tagList).slice(0, Mock.Random.integer(1, 3)),
+ totalSize: Mock.Random.integer(1024, 1024 * 1024 * 1024), // in bytes
+ description: Mock.Random.cparagraph(1, 3),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ createdBy: Mock.Random.cname(),
+ updatedBy: Mock.Random.cname(),
+ };
+}
+
+const datasetList = new Array(50)
+ .fill(null)
+ .map((_, index) => datasetItem(index));
+
+function datasetFileItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ fileName:
+ Mock.Random.word(5, 15) +
+ "." +
+ Mock.Random.pick(["csv", "json", "xml", "parquet", "avro"]),
+ originName:
+ Mock.Random.word(5, 15) +
+ "." +
+ Mock.Random.pick(["csv", "json", "xml", "parquet", "avro"]),
+ fileType: Mock.Random.pick(["CSV", "JSON", "XML", "Parquet", "Avro"]),
+ size: Mock.Random.integer(1024, 1024 * 1024 * 1024), // in bytes
+ type: Mock.Random.pick(["CSV", "JSON", "XML", "Parquet", "Avro"]),
+ status: Mock.Random.pick(["UPLOADED", "PROCESSING", "COMPLETED", "ERROR"]),
+ description: Mock.Random.csentence(5, 20),
+ filePath: "/path/to/file/" + Mock.Random.word(5, 10),
+ uploadedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ uploadedBy: Mock.Random.cname(),
+ };
+}
+
+const datasetFileList = new Array(200)
+ .fill(null)
+ .map((_, index) => datasetFileItem(index));
+
+const datasetStatistics = {
+ count: {
+ text: 10,
+ image: 34,
+ audio: 23,
+ video: 5,
+ },
+ size: {
+ text: "120 MB",
+ image: "3.4 GB",
+ audio: "2.3 GB",
+ video: "15 GB",
+ },
+ totalDatasets: datasetList.length,
+ totalFiles: datasetFileList.length,
+ completedFiles: datasetFileList.filter((file) => file.status === "COMPLETED")
+ .length,
+ totalSize: datasetFileList.reduce((acc, file) => acc + file.size, 0), // in bytes
+ completionRate:
+ datasetFileList.length === 0
+ ? 0
+ : Math.round(
+ (datasetFileList.filter((file) => file.status === "COMPLETED")
+ .length /
+ datasetFileList.length) *
+ 100
+ ), // percentage
+};
+
+const datasetTypes = [
+ {
+ code: "PRETRAIN",
+ name: "预训练数据集",
+ description: "用于模型预训练的大规模数据集",
+ supportedFormats: ["txt", "json", "csv", "parquet"],
+ icon: "brain",
+ },
+ {
+ code: "FINE_TUNE",
+ name: "微调数据集",
+ description: "用于模型微调的专业数据集",
+ supportedFormats: ["json", "csv", "xlsx"],
+ icon: "tune",
+ },
+ {
+ code: "EVAL",
+ name: "评估数据集",
+ description: "用于模型评估的标准数据集",
+ supportedFormats: ["json", "csv", "xml"],
+ icon: "assessment",
+ },
+];
+
+module.exports = { datasetList };
+module.exports = function (router) {
+ // 获取数据统计信息
+ router.get(API.queryDatasetStatisticsUsingGet, (req, res) => {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: datasetStatistics,
+ });
+ });
+
+ // 创建数据
+ router.post(API.createDatasetUsingPost, (req, res) => {
+ const newDataset = {
+ ...req.body,
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ status: "ACTIVE",
+ fileCount: 0,
+ totalSize: 0,
+ completionRate: 0,
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ createdBy: "Admin",
+ updatedBy: "Admin",
+ tags: tagList.filter((tag) => req.body?.tagIds?.includes?.(tag.id)),
+ };
+ datasetList.unshift(newDataset); // Add to the beginning of the list
+ res.send({
+ code: "0",
+ msg: "Dataset created successfully",
+ data: newDataset,
+ });
+ });
+
+ // 获取数据集列表
+ router.get(API.queryDatasetsUsingGet, (req, res) => {
+ const { page = 0, size = 10, keyword, type, status, tags } = req.query;
+ console.log("Received query params:", req.query);
+
+ let filteredDatasets = datasetList;
+ if (keyword) {
+ console.log("filter keyword:", keyword);
+
+ filteredDatasets = filteredDatasets.filter(
+ (dataset) =>
+ dataset.name.includes(keyword) ||
+ dataset.description.includes(keyword)
+ );
+ }
+ if (type) {
+ filteredDatasets = filteredDatasets.filter(
+ (dataset) => dataset.datasetType === type
+ );
+ }
+ if (status) {
+ console.log("filter status:", status);
+ filteredDatasets = filteredDatasets.filter(
+ (dataset) => dataset.status === status
+ );
+ }
+ if (tags && tags.length > 0) {
+ console.log("filter tags:", tags);
+ filteredDatasets = filteredDatasets.filter((dataset) =>
+ tags.every((tag) => dataset.tags.some((t) => t.name === tag))
+ );
+ }
+
+ const totalElements = filteredDatasets.length;
+ const paginatedDatasets = filteredDatasets.slice(
+ page * size,
+ (page + 1) * size
+ );
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ totalElements,
+ page,
+ size,
+ content: paginatedDatasets,
+ },
+ });
+ });
+
+ // 根据ID获取数据集详情
+ router.get(API.queryDatasetByIdUsingGet, (req, res) => {
+ const { id } = req.params;
+
+ const dataset = datasetList.find((d) => d.id === id);
+ if (dataset) {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: dataset,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Dataset not found",
+ data: null,
+ });
+ }
+ });
+
+ // 更新数据集
+ router.put(API.updateDatasetByIdUsingPut, (req, res) => {
+ const { id } = req.params;
+ let { tags } = req.body;
+
+ const index = datasetList.findIndex((d) => d.id === id);
+ tags = [...datasetList[index].tags.map((tag) => tag.name), ...tags];
+ if (index !== -1) {
+ datasetList[index] = {
+ ...datasetList[index],
+ ...req.body,
+ tags: tagList.filter((tag) => tags?.includes?.(tag.name)),
+ updatedAt: new Date().toISOString(),
+ updatedBy: "Admin",
+ };
+ res.send({
+ code: "0",
+ msg: "Dataset updated successfully",
+ data: datasetList[index],
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Dataset not found",
+ data: null,
+ });
+ }
+ });
+
+ // 删除数据集
+ router.delete(API.deleteDatasetByIdUsingDelete, (req, res) => {
+ const { datasetId } = req.params;
+ const index = datasetList.findIndex((d) => d.id === datasetId);
+
+ if (index !== -1) {
+ datasetList.splice(index, 1);
+ res.status(204).send();
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Dataset not found",
+ data: null,
+ });
+ }
+ });
+
+ // 获取数据集文件列表
+ router.get(API.queryFilesUsingGet, (req, res) => {
+ const { datasetId } = req.params;
+ const { page = 0, size = 20, fileType, status } = req.query;
+
+ let filteredFiles = datasetFileList;
+
+ if (fileType) {
+ filteredFiles = filteredFiles.filter(
+ (file) => file.fileType === fileType
+ );
+ }
+
+ if (status) {
+ filteredFiles = filteredFiles.filter((file) => file.status === status);
+ }
+
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = filteredFiles.slice(startIndex, endIndex);
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: pageData,
+ page: parseInt(page),
+ size: parseInt(size),
+ totalElements: filteredFiles.length,
+ },
+ });
+ });
+
+ // 上传文件到数据集
+ router.post(API.uploadFileUsingPost, (req, res) => {
+ const { datasetId } = req.params;
+ const newFile = {
+ ...datasetFileItem(),
+ ...req.body,
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ uploadedAt: new Date().toISOString(),
+ uploadedBy: "Admin",
+ };
+
+ datasetFileList.push(newFile);
+
+ res.status(201).send({
+ code: "0",
+ msg: "File uploaded successfully",
+ data: newFile,
+ });
+ });
+
+ // 获取文件详情
+ router.get(API.queryFileByIdUsingGet, (req, res) => {
+ const { datasetId, fileId } = req.params;
+ const file = datasetFileList.find((f) => f.id === fileId);
+
+ if (file) {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: file,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "File not found",
+ data: null,
+ });
+ }
+ });
+
+ // 删除文件
+ router.delete(API.deleteFileByIdUsingDelete, (req, res) => {
+ const { datasetId, fileId } = req.params;
+ const index = datasetFileList.findIndex((f) => f.id === fileId);
+
+ if (index !== -1) {
+ datasetFileList.splice(index, 1);
+ res.status(204).send();
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "File not found",
+ data: null,
+ });
+ }
+ });
+
+ // 下载文件
+ router.get(API.downloadFileByIdUsingGet, (req, res) => {
+ const { datasetId, fileId } = req.params;
+ const file = datasetFileList.find((f) => f.id === fileId);
+
+ if (file) {
+ res.setHeader(
+ "Content-Disposition",
+ `attachment; filename="${file.fileName}"`
+ );
+ res.setHeader("Content-Type", "application/octet-stream");
+ res.send(`Mock file content for ${file.fileName}`);
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "File not found",
+ data: null,
+ });
+ }
+ });
+
+ // 获取数据集类型列表
+ router.get(API.queryDatasetTypesUsingGet, (req, res) => {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: datasetTypes,
+ });
+ });
+
+ // 获取标签列表
+ router.get(API.queryTagsUsingGet, (req, res) => {
+ const { keyword } = req.query;
+ let filteredTags = tagList;
+
+ if (keyword) {
+ filteredTags = tagList.filter((tag) =>
+ tag.name.toLowerCase().includes(keyword.toLowerCase())
+ );
+ }
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: filteredTags,
+ });
+ });
+
+ // 创建标签
+ router.post(API.createTagUsingPost, (req, res) => {
+ const newTag = {
+ ...tagItem(),
+ ...req.body,
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ usageCount: 0,
+ };
+
+ tagList.push(newTag);
+
+ res.status(201).send({
+ code: "0",
+ msg: "Tag created successfully",
+ data: newTag,
+ });
+ });
+
+ router.post(API.preUploadFileUsingPost, (req, res) => {
+ res.status(201).send(Mock.Random.guid());
+ });
+
+ // 上传
+ router.post(API.uploadFileChunkUsingPost, (req, res) => {
+ res.status(500).send({ message: "Simulated upload failure" });
+ // res.status(201).send({ data: "success" });
+ });
+
+ // 取消上传
+ router.put(API.cancelUploadUsingPut, (req, res) => {
+ res.status(201).send({ data: "success" });
+ });
+};
diff --git a/frontend/src/mock/mock-seed/data-ratio.cjs b/frontend/src/mock/mock-seed/data-ratio.cjs
index 7592f8a76..9ca4b3aec 100644
--- a/frontend/src/mock/mock-seed/data-ratio.cjs
+++ b/frontend/src/mock/mock-seed/data-ratio.cjs
@@ -1,220 +1,220 @@
-
-const Mock = require("mockjs");
-const API = require("../mock-apis.cjs");
-
-function ratioJobItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.ctitle(5, 15),
- description: Mock.Random.csentence(10, 30),
- status: Mock.Random.pick(["PENDING", "RUNNING", "COMPLETED", "FAILED", "PAUSED"]),
- totals: Mock.Random.integer(1000, 10000),
- ratio_method: Mock.Random.pick(["DATASET", "TAG"]),
- target_dataset_id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- target_dataset_name: Mock.Random.ctitle(3, 8),
- config: [
- {
- datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- counts: Mock.Random.integer(100, 1000).toString(),
- filter_conditions: "",
- },
- {
- datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- counts: Mock.Random.integer(100, 1000).toString(),
- filter_conditions: "",
- },
- ],
- created_at: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updated_at: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- };
-}
-
-const ratioJobList = new Array(20).fill(null).map(ratioJobItem);
-
-
-module.exports = function (router) {
- // 获取配比任务列表
- router.get(API.queryRatioTasksUsingGet, (req, res) => {
- const { page = 0, size = 10, status } = req.query;
- let filteredJobs = ratioJobList;
- if (status) {
- filteredJobs = ratioJobList.filter((job) => job.status === status);
- }
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = filteredJobs.slice(startIndex, endIndex);
- res.send({
- code: "0",
- msg: "Success",
- data: {
- content: pageData,
- totalElements: filteredJobs.length,
- totalPages: Math.ceil(filteredJobs.length / size),
- size: parseInt(size),
- number: parseInt(page),
- },
- });
- });
-
- // 创建配比任务
- router.post(API.createRatioTaskUsingPost, (req, res) => {
- const newJob = {
- ...ratioJobItem(),
- ...req.body,
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- status: "PENDING",
- createdAt: new Date().toISOString(),
- };
- ratioJobList.push(newJob);
- res.status(201).send({
- code: "0",
- msg: "Ratio job created successfully",
- data: newJob,
- });
- });
-
- // 获取配比任务详情
- router.get(API.queryRatioTaskByIdUsingGet, (req, res) => {
- const { taskId } = req.params;
- const job = ratioJobList.find((j) => j.id === taskId);
- if (job) {
- res.send({
- code: "0",
- msg: "Success",
- data: job,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Ratio job not found",
- data: null,
- });
- }
- });
-
- // 删除配比任务
- router.delete(API.deleteRatioTaskByIdUsingDelete, (req, res) => {
- const { taskId } = req.params;
- const index = ratioJobList.findIndex((j) => j.id === taskId);
- if (index !== -1) {
- ratioJobList.splice(index, 1);
- res.send({
- code: "0",
- msg: "Ratio job deleted successfully",
- data: null,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Ratio job not found",
- data: null,
- });
- }
- });
-
- // 更新配比任务
- router.put(API.updateRatioTaskByIdUsingPut, (req, res) => {
- const { taskId } = req.params;
- const index = ratioJobList.findIndex((j) => j.id === taskId);
- if (index !== -1) {
- ratioJobList[index] = {
- ...ratioJobList[index],
- ...req.body,
- updatedAt: new Date().toISOString(),
- };
- res.send({
- code: "0",
- msg: "Ratio job updated successfully",
- data: ratioJobList[index],
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Ratio job not found",
- data: null,
- });
- }
- });
-
- // 执行配比任务
- router.post(API.executeRatioTaskByIdUsingPost, (req, res) => {
- const { taskId } = req.params;
- const job = ratioJobList.find((j) => j.id === taskId);
- if (job) {
- job.status = "RUNNING";
- job.startedAt = new Date().toISOString();
- res.send({
- code: "0",
- msg: "Ratio job execution started",
- data: {
- executionId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- status: "RUNNING",
- message: "Job execution started successfully",
- },
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Ratio job not found",
- data: null,
- });
- }
- });
-
- // 停止配比任务
- router.post(API.stopRatioTaskByIdUsingPost, (req, res) => {
- const { taskId } = req.params;
- const job = ratioJobList.find((j) => j.id === taskId);
- if (job) {
- job.status = "STOPPED";
- job.finishedAt = new Date().toISOString();
- res.send({
- code: "0",
- msg: "Ratio job stopped successfully",
- data: null,
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Ratio job not found",
- data: null,
- });
- }
- });
-
- // 获取配比任务状态
- router.get(API.queryRatioJobStatusUsingGet, (req, res) => {
- const { taskId } = req.params;
- const job = ratioJobList.find((j) => j.id === taskId);
- if (job) {
- res.send({
- code: "0",
- msg: "Success",
- data: {
- status: job.status,
- progress: job.progress,
- },
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Ratio job not found",
- data: null,
- });
- }
- });
-
- // 获取配比模型列表
- router.get(API.queryRatioModelsUsingGet, (req, res) => {
- const models = [
- { id: "model1", name: "均匀分配模型", description: "将目标数量均匀分配到各数据集。" },
- { id: "model2", name: "标签优先模型", description: "优先满足标签配比需求。" },
- { id: "model3", name: "自定义模型", description: "支持自定义分配逻辑。" },
- ];
- res.send({
- code: "0",
- msg: "Success",
- data: models,
- });
- });
-};
+
+const Mock = require("mockjs");
+const API = require("../mock-apis.cjs");
+
+function ratioJobItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.ctitle(5, 15),
+ description: Mock.Random.csentence(10, 30),
+ status: Mock.Random.pick(["PENDING", "RUNNING", "COMPLETED", "FAILED", "PAUSED"]),
+ totals: Mock.Random.integer(1000, 10000),
+ ratio_method: Mock.Random.pick(["DATASET", "TAG"]),
+ target_dataset_id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ target_dataset_name: Mock.Random.ctitle(3, 8),
+ config: [
+ {
+ datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ counts: Mock.Random.integer(100, 1000).toString(),
+ filter_conditions: "",
+ },
+ {
+ datasetId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ counts: Mock.Random.integer(100, 1000).toString(),
+ filter_conditions: "",
+ },
+ ],
+ created_at: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updated_at: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ };
+}
+
+const ratioJobList = new Array(20).fill(null).map(ratioJobItem);
+
+
+module.exports = function (router) {
+ // 获取配比任务列表
+ router.get(API.queryRatioTasksUsingGet, (req, res) => {
+ const { page = 0, size = 10, status } = req.query;
+ let filteredJobs = ratioJobList;
+ if (status) {
+ filteredJobs = ratioJobList.filter((job) => job.status === status);
+ }
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = filteredJobs.slice(startIndex, endIndex);
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: pageData,
+ totalElements: filteredJobs.length,
+ totalPages: Math.ceil(filteredJobs.length / size),
+ size: parseInt(size),
+ number: parseInt(page),
+ },
+ });
+ });
+
+ // 创建配比任务
+ router.post(API.createRatioTaskUsingPost, (req, res) => {
+ const newJob = {
+ ...ratioJobItem(),
+ ...req.body,
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ status: "PENDING",
+ createdAt: new Date().toISOString(),
+ };
+ ratioJobList.push(newJob);
+ res.status(201).send({
+ code: "0",
+ msg: "Ratio job created successfully",
+ data: newJob,
+ });
+ });
+
+ // 获取配比任务详情
+ router.get(API.queryRatioTaskByIdUsingGet, (req, res) => {
+ const { taskId } = req.params;
+ const job = ratioJobList.find((j) => j.id === taskId);
+ if (job) {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: job,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Ratio job not found",
+ data: null,
+ });
+ }
+ });
+
+ // 删除配比任务
+ router.delete(API.deleteRatioTaskByIdUsingDelete, (req, res) => {
+ const { taskId } = req.params;
+ const index = ratioJobList.findIndex((j) => j.id === taskId);
+ if (index !== -1) {
+ ratioJobList.splice(index, 1);
+ res.send({
+ code: "0",
+ msg: "Ratio job deleted successfully",
+ data: null,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Ratio job not found",
+ data: null,
+ });
+ }
+ });
+
+ // 更新配比任务
+ router.put(API.updateRatioTaskByIdUsingPut, (req, res) => {
+ const { taskId } = req.params;
+ const index = ratioJobList.findIndex((j) => j.id === taskId);
+ if (index !== -1) {
+ ratioJobList[index] = {
+ ...ratioJobList[index],
+ ...req.body,
+ updatedAt: new Date().toISOString(),
+ };
+ res.send({
+ code: "0",
+ msg: "Ratio job updated successfully",
+ data: ratioJobList[index],
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Ratio job not found",
+ data: null,
+ });
+ }
+ });
+
+ // 执行配比任务
+ router.post(API.executeRatioTaskByIdUsingPost, (req, res) => {
+ const { taskId } = req.params;
+ const job = ratioJobList.find((j) => j.id === taskId);
+ if (job) {
+ job.status = "RUNNING";
+ job.startedAt = new Date().toISOString();
+ res.send({
+ code: "0",
+ msg: "Ratio job execution started",
+ data: {
+ executionId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ status: "RUNNING",
+ message: "Job execution started successfully",
+ },
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Ratio job not found",
+ data: null,
+ });
+ }
+ });
+
+ // 停止配比任务
+ router.post(API.stopRatioTaskByIdUsingPost, (req, res) => {
+ const { taskId } = req.params;
+ const job = ratioJobList.find((j) => j.id === taskId);
+ if (job) {
+ job.status = "STOPPED";
+ job.finishedAt = new Date().toISOString();
+ res.send({
+ code: "0",
+ msg: "Ratio job stopped successfully",
+ data: null,
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Ratio job not found",
+ data: null,
+ });
+ }
+ });
+
+ // 获取配比任务状态
+ router.get(API.queryRatioJobStatusUsingGet, (req, res) => {
+ const { taskId } = req.params;
+ const job = ratioJobList.find((j) => j.id === taskId);
+ if (job) {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ status: job.status,
+ progress: job.progress,
+ },
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Ratio job not found",
+ data: null,
+ });
+ }
+ });
+
+ // 获取配比模型列表
+ router.get(API.queryRatioModelsUsingGet, (req, res) => {
+ const models = [
+ { id: "model1", name: "均匀分配模型", description: "将目标数量均匀分配到各数据集。" },
+ { id: "model2", name: "标签优先模型", description: "优先满足标签配比需求。" },
+ { id: "model3", name: "自定义模型", description: "支持自定义分配逻辑。" },
+ ];
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: models,
+ });
+ });
+};
diff --git a/frontend/src/mock/mock-seed/data-synthesis.cjs b/frontend/src/mock/mock-seed/data-synthesis.cjs
index 831758831..826acfc7d 100644
--- a/frontend/src/mock/mock-seed/data-synthesis.cjs
+++ b/frontend/src/mock/mock-seed/data-synthesis.cjs
@@ -1,522 +1,522 @@
-const Mock = require("mockjs");
-const API = require("../mock-apis.cjs");
-
-// 合成类型枚举
-const SynthesisTypes = [
- "INSTRUCTION_TUNING",
- "COT_DISTILLATION",
- "DIALOGUE_GENERATION",
- "TEXT_AUGMENTATION",
- "MULTIMODAL_SYNTHESIS",
- "CUSTOM"
-];
-
-// 任务状态枚举
-const JobStatuses = ["PENDING", "RUNNING", "COMPLETED", "FAILED", "CANCELLED"];
-
-// 模型配置
-function modelConfigItem() {
- return {
- modelName: Mock.Random.pick([
- "gpt-3.5-turbo",
- "gpt-4",
- "claude-3",
- "llama-2-70b",
- "qwen-max"
- ]),
- temperature: Mock.Random.float(0.1, 1.0, 2, 2),
- maxTokens: Mock.Random.pick([512, 1024, 2048, 4096]),
- topP: Mock.Random.float(0.1, 1.0, 2, 2),
- frequencyPenalty: Mock.Random.float(0, 2.0, 2, 2)
- };
-}
-
-// 合成模板数据
-function synthesisTemplateItem() {
- const type = Mock.Random.pick(SynthesisTypes);
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.ctitle(5, 20),
- description: Mock.Random.csentence(5, 30),
- type,
- category: Mock.Random.pick([
- "教育培训", "对话系统", "内容生成", "代码生成", "多模态", "自定义"
- ]),
- modelConfig: modelConfigItem(),
- enabled: Mock.Random.boolean(),
- promptTemplate: type === "INSTRUCTION_TUNING"
- ? "请根据以下主题生成一个指令:{topic}\n指令应该包含:\n1. 明确的任务描述\n2. 具体的输入要求\n3. 期望的输出格式"
- : type === "COT_DISTILLATION"
- ? "问题:{question}\n请提供详细的推理步骤,然后给出最终答案。\n推理过程:\n1. 分析问题的关键信息\n2. 应用相关知识和规则\n3. 逐步推导出结论"
- : "请根据以下模板生成内容:{template}",
- parameters: {
- maxLength: Mock.Random.integer(100, 2000),
- diversity: Mock.Random.float(0.1, 1.0, 2, 2),
- quality: Mock.Random.float(0.7, 1.0, 2, 2)
- },
- examples: new Array(Mock.Random.integer(2, 5)).fill(null).map(() => ({
- input: Mock.Random.csentence(10, 30),
- output: Mock.Random.csentence(20, 50),
- explanation: Mock.Random.csentence(5, 20)
- })),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss")
- };
-}
-
-const synthesisTemplateList = new Array(25).fill(null).map(synthesisTemplateItem);
-
-// 合成任务数据
-function synthesisJobItem() {
- const template = Mock.Random.pick(synthesisTemplateList);
- const targetCount = Mock.Random.integer(100, 10000);
- const generatedCount = Mock.Random.integer(0, targetCount);
- const progress = targetCount > 0 ? (generatedCount / targetCount) * 100 : 0;
-
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.ctitle(5, 20),
- description: Mock.Random.csentence(5, 30),
- templateId: template.id,
- template: {
- id: template.id,
- name: template.name,
- type: template.type
- },
- status: Mock.Random.pick(JobStatuses),
- progress: Math.round(progress * 100) / 100,
- targetCount,
- generatedCount,
- startTime: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- endTime: progress >= 100 ? Mock.Random.datetime("yyyy-MM-dd HH:mm:ss") : null,
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- statistics: {
- totalGenerated: generatedCount,
- successfulGenerated: Math.floor(generatedCount * Mock.Random.float(0.85, 0.98, 2, 2)),
- failedGenerated: Math.floor(generatedCount * Mock.Random.float(0.02, 0.15, 2, 2)),
- averageLength: Mock.Random.integer(50, 500),
- uniqueCount: Math.floor(generatedCount * Mock.Random.float(0.8, 0.95, 2, 2))
- },
- samples: new Array(Math.min(10, generatedCount)).fill(null).map(() => ({
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- content: Mock.Random.cparagraph(1, 3),
- score: Mock.Random.float(0.6, 1.0, 2, 2),
- metadata: {
- length: Mock.Random.integer(50, 500),
- model: template.modelConfig.modelName,
- timestamp: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss")
- },
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss")
- }))
- };
-}
-
-const synthesisJobList = new Array(30).fill(null).map(synthesisJobItem);
-
-// 生成指令数据
-function generatedInstructionItem() {
- return {
- instruction: Mock.Random.csentence(10, 30),
- input: Mock.Random.csentence(5, 20),
- output: Mock.Random.csentence(10, 40),
- quality: Mock.Random.float(0.7, 1.0, 2, 2)
- };
-}
-
-// COT 示例数据
-function cotExampleItem() {
- return {
- question: Mock.Random.csentence(10, 25) + "?",
- reasoning: Mock.Random.cparagraph(2, 4),
- answer: Mock.Random.csentence(5, 15)
- };
-}
-
-// 蒸馏COT数据
-function distilledCOTDataItem() {
- return {
- question: Mock.Random.csentence(10, 25) + "?",
- reasoning: Mock.Random.cparagraph(2, 4),
- answer: Mock.Random.csentence(5, 15),
- confidence: Mock.Random.float(0.7, 1.0, 2, 2)
- };
-}
-
-module.exports = function (router) {
- // 获取合成模板列表
- router.get(API.querySynthesisTemplatesUsingGet, (req, res) => {
- const { page = 0, size = 20, type } = req.query;
- let filteredTemplates = synthesisTemplateList;
-
- if (type) {
- filteredTemplates = synthesisTemplateList.filter(
- (template) => template.type === type
- );
- }
-
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = filteredTemplates.slice(startIndex, endIndex);
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- content: pageData,
- totalElements: filteredTemplates.length,
- totalPages: Math.ceil(filteredTemplates.length / size),
- size: parseInt(size),
- number: parseInt(page)
- }
- });
- });
-
- // 创建合成模板
- router.post(API.createSynthesisTemplateUsingPost, (req, res) => {
- const newTemplate = {
- ...synthesisTemplateItem(),
- ...req.body,
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- enabled: true,
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString()
- };
- synthesisTemplateList.push(newTemplate);
-
- res.status(201).send({
- code: "0",
- msg: "Synthesis template created successfully",
- data: newTemplate
- });
- });
-
- // 获取合成模板详情
- router.get(API.querySynthesisTemplateByIdUsingGet, (req, res) => {
- const { templateId } = req.params;
- const template = synthesisTemplateList.find((t) => t.id === templateId);
-
- if (template) {
- res.send({
- code: "0",
- msg: "Success",
- data: template
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Synthesis template not found",
- data: null
- });
- }
- });
-
- // 更新合成模板
- router.put(API.updateSynthesisTemplateByIdUsingPut, (req, res) => {
- const { templateId } = req.params;
- const index = synthesisTemplateList.findIndex((t) => t.id === templateId);
-
- if (index !== -1) {
- synthesisTemplateList[index] = {
- ...synthesisTemplateList[index],
- ...req.body,
- updatedAt: new Date().toISOString()
- };
- res.send({
- code: "0",
- msg: "Synthesis template updated successfully",
- data: synthesisTemplateList[index]
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Synthesis template not found",
- data: null
- });
- }
- });
-
- // 删除合成模板
- router.delete(API.deleteSynthesisTemplateByIdUsingDelete, (req, res) => {
- const { templateId } = req.params;
- const index = synthesisTemplateList.findIndex((t) => t.id === templateId);
-
- if (index !== -1) {
- synthesisTemplateList.splice(index, 1);
- res.send({
- code: "0",
- msg: "Synthesis template deleted successfully",
- data: null
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Synthesis template not found",
- data: null
- });
- }
- });
-
- // 获取合成任务列表
- router.get(API.querySynthesisJobsUsingGet, (req, res) => {
- const { page = 0, size = 20, status } = req.query;
- let filteredJobs = synthesisJobList;
-
- if (status) {
- filteredJobs = synthesisJobList.filter((job) => job.status === status);
- }
-
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = filteredJobs.slice(startIndex, endIndex);
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- content: pageData,
- totalElements: filteredJobs.length,
- totalPages: Math.ceil(filteredJobs.length / size),
- size: parseInt(size),
- number: parseInt(page)
- }
- });
- });
-
- // 创建合成任务
- router.post(API.createSynthesisJobUsingPost, (req, res) => {
- const { templateId } = req.body;
- const template = synthesisTemplateList.find(t => t.id === templateId);
-
- const newJob = {
- ...synthesisJobItem(),
- ...req.body,
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- templateId,
- template: template ? {
- id: template.id,
- name: template.name,
- type: template.type
- } : null,
- status: "PENDING",
- progress: 0,
- generatedCount: 0,
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString()
- };
- synthesisJobList.push(newJob);
-
- res.status(201).send({
- code: "0",
- msg: "Synthesis job created successfully",
- data: newJob
- });
- });
-
- // 获取合成任务详情
- router.get(API.querySynthesisJobByIdUsingGet, (req, res) => {
- const { jobId } = req.params;
- const job = synthesisJobList.find((j) => j.id === jobId);
-
- if (job) {
- const template = synthesisTemplateList.find(t => t.id === job.templateId);
- const jobDetail = {
- ...job,
- template: template || null
- };
-
- res.send({
- code: "0",
- msg: "Success",
- data: jobDetail
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Synthesis job not found",
- data: null
- });
- }
- });
-
- // 删除合成任务
- router.delete(API.deleteSynthesisJobByIdUsingDelete, (req, res) => {
- const { jobId } = req.params;
- const index = synthesisJobList.findIndex((j) => j.id === jobId);
-
- if (index !== -1) {
- synthesisJobList.splice(index, 1);
- res.send({
- code: "0",
- msg: "Synthesis job deleted successfully",
- data: null
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Synthesis job not found",
- data: null
- });
- }
- });
-
- // 执行合成任务
- router.post(API.executeSynthesisJobUsingPost, (req, res) => {
- const { jobId } = req.params;
- const job = synthesisJobList.find((j) => j.id === jobId);
-
- if (job) {
- job.status = "RUNNING";
- job.startTime = new Date().toISOString();
- job.updatedAt = new Date().toISOString();
-
- // 模拟异步执行
- setTimeout(() => {
- job.status = Mock.Random.pick(["COMPLETED", "FAILED"]);
- job.progress = job.status === "COMPLETED" ? 100 : Mock.Random.float(10, 90, 2, 2);
- job.generatedCount = Math.floor((job.progress / 100) * job.targetCount);
- job.endTime = new Date().toISOString();
- }, Mock.Random.integer(2000, 5000));
-
- res.send({
- code: "0",
- msg: "Synthesis job execution started",
- data: {
- executionId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- status: "RUNNING",
- message: "Job execution started successfully"
- }
- });
- } else {
- res.status(404).send({
- code: "1",
- msg: "Synthesis job not found",
- data: null
- });
- }
- });
-
- // 指令调优数据合成
- router.post(API.instructionTuningUsingPost, (req, res) => {
- const { baseInstructions, targetDomain, count, modelConfig, parameters } = req.body;
-
- const jobId = Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, "");
- const generatedInstructions = new Array(count).fill(null).map(() => generatedInstructionItem());
-
- const statistics = {
- totalGenerated: count,
- averageQuality: Mock.Random.float(0.8, 0.95, 2, 2),
- diversityScore: Mock.Random.float(0.7, 0.9, 2, 2)
- };
-
- res.send({
- code: "0",
- msg: "Instruction tuning completed successfully",
- data: {
- jobId,
- generatedInstructions,
- statistics
- }
- });
- });
-
- // COT蒸馏数据合成
- router.post(API.cotDistillationUsingPost, (req, res) => {
- const { sourceModel, targetFormat, examples, parameters } = req.body;
-
- const jobId = Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, "");
- const processedCount = examples.length;
- const successfulCount = Math.floor(processedCount * Mock.Random.float(0.85, 0.98, 2, 2));
-
- const distilledData = new Array(successfulCount).fill(null).map(() => distilledCOTDataItem());
-
- const statistics = {
- totalProcessed: processedCount,
- successfulDistilled: successfulCount,
- averageConfidence: Mock.Random.float(0.8, 0.95, 2, 2)
- };
-
- res.send({
- code: "0",
- msg: "COT distillation completed successfully",
- data: {
- jobId,
- distilledData,
- statistics
- }
- });
- });
-
- // 获取合成任务统计信息
- router.get("/api/v1/synthesis/statistics", (req, res) => {
- const statistics = {
- totalJobs: synthesisJobList.length,
- completedJobs: synthesisJobList.filter(j => j.status === "COMPLETED").length,
- runningJobs: synthesisJobList.filter(j => j.status === "RUNNING").length,
- failedJobs: synthesisJobList.filter(j => j.status === "FAILED").length,
- totalGenerated: synthesisJobList.reduce((sum, job) => sum + job.generatedCount, 0),
- averageQuality: Mock.Random.float(0.8, 0.95, 2, 2),
- templateTypeDistribution: {
- "INSTRUCTION_TUNING": synthesisTemplateList.filter(t => t.type === "INSTRUCTION_TUNING").length,
- "COT_DISTILLATION": synthesisTemplateList.filter(t => t.type === "COT_DISTILLATION").length,
- "DIALOGUE_GENERATION": synthesisTemplateList.filter(t => t.type === "DIALOGUE_GENERATION").length,
- "TEXT_AUGMENTATION": synthesisTemplateList.filter(t => t.type === "TEXT_AUGMENTATION").length,
- "MULTIMODAL_SYNTHESIS": synthesisTemplateList.filter(t => t.type === "MULTIMODAL_SYNTHESIS").length,
- "CUSTOM": synthesisTemplateList.filter(t => t.type === "CUSTOM").length
- },
- dailyGeneration: new Array(7).fill(null).map((_, index) => ({
- date: Mock.Random.date("yyyy-MM-dd"),
- count: Mock.Random.integer(100, 5000)
- }))
- };
-
- res.send({
- code: "0",
- msg: "Success",
- data: statistics
- });
- });
-
- // 批量操作
- router.post("/api/v1/synthesis/jobs/batch", (req, res) => {
- const { action, jobIds } = req.body;
-
- let successCount = 0;
- let failedCount = 0;
-
- jobIds.forEach(jobId => {
- const job = synthesisJobList.find(j => j.id === jobId);
- if (job) {
- switch(action) {
- case "DELETE":
- const index = synthesisJobList.findIndex(j => j.id === jobId);
- synthesisJobList.splice(index, 1);
- successCount++;
- break;
- case "START":
- job.status = "RUNNING";
- job.startTime = new Date().toISOString();
- successCount++;
- break;
- case "STOP":
- job.status = "CANCELLED";
- job.endTime = new Date().toISOString();
- successCount++;
- break;
- }
- } else {
- failedCount++;
- }
- });
-
- res.send({
- code: "0",
- msg: `Batch ${action.toLowerCase()} completed`,
- data: {
- total: jobIds.length,
- success: successCount,
- failed: failedCount
- }
- });
- });
+const Mock = require("mockjs");
+const API = require("../mock-apis.cjs");
+
+// 合成类型枚举
+const SynthesisTypes = [
+ "INSTRUCTION_TUNING",
+ "COT_DISTILLATION",
+ "DIALOGUE_GENERATION",
+ "TEXT_AUGMENTATION",
+ "MULTIMODAL_SYNTHESIS",
+ "CUSTOM"
+];
+
+// 任务状态枚举
+const JobStatuses = ["PENDING", "RUNNING", "COMPLETED", "FAILED", "CANCELLED"];
+
+// 模型配置
+function modelConfigItem() {
+ return {
+ modelName: Mock.Random.pick([
+ "gpt-3.5-turbo",
+ "gpt-4",
+ "claude-3",
+ "llama-2-70b",
+ "qwen-max"
+ ]),
+ temperature: Mock.Random.float(0.1, 1.0, 2, 2),
+ maxTokens: Mock.Random.pick([512, 1024, 2048, 4096]),
+ topP: Mock.Random.float(0.1, 1.0, 2, 2),
+ frequencyPenalty: Mock.Random.float(0, 2.0, 2, 2)
+ };
+}
+
+// 合成模板数据
+function synthesisTemplateItem() {
+ const type = Mock.Random.pick(SynthesisTypes);
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.ctitle(5, 20),
+ description: Mock.Random.csentence(5, 30),
+ type,
+ category: Mock.Random.pick([
+ "教育培训", "对话系统", "内容生成", "代码生成", "多模态", "自定义"
+ ]),
+ modelConfig: modelConfigItem(),
+ enabled: Mock.Random.boolean(),
+ promptTemplate: type === "INSTRUCTION_TUNING"
+ ? "请根据以下主题生成一个指令:{topic}\n指令应该包含:\n1. 明确的任务描述\n2. 具体的输入要求\n3. 期望的输出格式"
+ : type === "COT_DISTILLATION"
+ ? "问题:{question}\n请提供详细的推理步骤,然后给出最终答案。\n推理过程:\n1. 分析问题的关键信息\n2. 应用相关知识和规则\n3. 逐步推导出结论"
+ : "请根据以下模板生成内容:{template}",
+ parameters: {
+ maxLength: Mock.Random.integer(100, 2000),
+ diversity: Mock.Random.float(0.1, 1.0, 2, 2),
+ quality: Mock.Random.float(0.7, 1.0, 2, 2)
+ },
+ examples: new Array(Mock.Random.integer(2, 5)).fill(null).map(() => ({
+ input: Mock.Random.csentence(10, 30),
+ output: Mock.Random.csentence(20, 50),
+ explanation: Mock.Random.csentence(5, 20)
+ })),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss")
+ };
+}
+
+const synthesisTemplateList = new Array(25).fill(null).map(synthesisTemplateItem);
+
+// 合成任务数据
+function synthesisJobItem() {
+ const template = Mock.Random.pick(synthesisTemplateList);
+ const targetCount = Mock.Random.integer(100, 10000);
+ const generatedCount = Mock.Random.integer(0, targetCount);
+ const progress = targetCount > 0 ? (generatedCount / targetCount) * 100 : 0;
+
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.ctitle(5, 20),
+ description: Mock.Random.csentence(5, 30),
+ templateId: template.id,
+ template: {
+ id: template.id,
+ name: template.name,
+ type: template.type
+ },
+ status: Mock.Random.pick(JobStatuses),
+ progress: Math.round(progress * 100) / 100,
+ targetCount,
+ generatedCount,
+ startTime: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ endTime: progress >= 100 ? Mock.Random.datetime("yyyy-MM-dd HH:mm:ss") : null,
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ statistics: {
+ totalGenerated: generatedCount,
+ successfulGenerated: Math.floor(generatedCount * Mock.Random.float(0.85, 0.98, 2, 2)),
+ failedGenerated: Math.floor(generatedCount * Mock.Random.float(0.02, 0.15, 2, 2)),
+ averageLength: Mock.Random.integer(50, 500),
+ uniqueCount: Math.floor(generatedCount * Mock.Random.float(0.8, 0.95, 2, 2))
+ },
+ samples: new Array(Math.min(10, generatedCount)).fill(null).map(() => ({
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ content: Mock.Random.cparagraph(1, 3),
+ score: Mock.Random.float(0.6, 1.0, 2, 2),
+ metadata: {
+ length: Mock.Random.integer(50, 500),
+ model: template.modelConfig.modelName,
+ timestamp: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss")
+ },
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss")
+ }))
+ };
+}
+
+const synthesisJobList = new Array(30).fill(null).map(synthesisJobItem);
+
+// 生成指令数据
+function generatedInstructionItem() {
+ return {
+ instruction: Mock.Random.csentence(10, 30),
+ input: Mock.Random.csentence(5, 20),
+ output: Mock.Random.csentence(10, 40),
+ quality: Mock.Random.float(0.7, 1.0, 2, 2)
+ };
+}
+
+// COT 示例数据
+function cotExampleItem() {
+ return {
+ question: Mock.Random.csentence(10, 25) + "?",
+ reasoning: Mock.Random.cparagraph(2, 4),
+ answer: Mock.Random.csentence(5, 15)
+ };
+}
+
+// 蒸馏COT数据
+function distilledCOTDataItem() {
+ return {
+ question: Mock.Random.csentence(10, 25) + "?",
+ reasoning: Mock.Random.cparagraph(2, 4),
+ answer: Mock.Random.csentence(5, 15),
+ confidence: Mock.Random.float(0.7, 1.0, 2, 2)
+ };
+}
+
+module.exports = function (router) {
+ // 获取合成模板列表
+ router.get(API.querySynthesisTemplatesUsingGet, (req, res) => {
+ const { page = 0, size = 20, type } = req.query;
+ let filteredTemplates = synthesisTemplateList;
+
+ if (type) {
+ filteredTemplates = synthesisTemplateList.filter(
+ (template) => template.type === type
+ );
+ }
+
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = filteredTemplates.slice(startIndex, endIndex);
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: pageData,
+ totalElements: filteredTemplates.length,
+ totalPages: Math.ceil(filteredTemplates.length / size),
+ size: parseInt(size),
+ number: parseInt(page)
+ }
+ });
+ });
+
+ // 创建合成模板
+ router.post(API.createSynthesisTemplateUsingPost, (req, res) => {
+ const newTemplate = {
+ ...synthesisTemplateItem(),
+ ...req.body,
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ enabled: true,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString()
+ };
+ synthesisTemplateList.push(newTemplate);
+
+ res.status(201).send({
+ code: "0",
+ msg: "Synthesis template created successfully",
+ data: newTemplate
+ });
+ });
+
+ // 获取合成模板详情
+ router.get(API.querySynthesisTemplateByIdUsingGet, (req, res) => {
+ const { templateId } = req.params;
+ const template = synthesisTemplateList.find((t) => t.id === templateId);
+
+ if (template) {
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: template
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Synthesis template not found",
+ data: null
+ });
+ }
+ });
+
+ // 更新合成模板
+ router.put(API.updateSynthesisTemplateByIdUsingPut, (req, res) => {
+ const { templateId } = req.params;
+ const index = synthesisTemplateList.findIndex((t) => t.id === templateId);
+
+ if (index !== -1) {
+ synthesisTemplateList[index] = {
+ ...synthesisTemplateList[index],
+ ...req.body,
+ updatedAt: new Date().toISOString()
+ };
+ res.send({
+ code: "0",
+ msg: "Synthesis template updated successfully",
+ data: synthesisTemplateList[index]
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Synthesis template not found",
+ data: null
+ });
+ }
+ });
+
+ // 删除合成模板
+ router.delete(API.deleteSynthesisTemplateByIdUsingDelete, (req, res) => {
+ const { templateId } = req.params;
+ const index = synthesisTemplateList.findIndex((t) => t.id === templateId);
+
+ if (index !== -1) {
+ synthesisTemplateList.splice(index, 1);
+ res.send({
+ code: "0",
+ msg: "Synthesis template deleted successfully",
+ data: null
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Synthesis template not found",
+ data: null
+ });
+ }
+ });
+
+ // 获取合成任务列表
+ router.get(API.querySynthesisJobsUsingGet, (req, res) => {
+ const { page = 0, size = 20, status } = req.query;
+ let filteredJobs = synthesisJobList;
+
+ if (status) {
+ filteredJobs = synthesisJobList.filter((job) => job.status === status);
+ }
+
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = filteredJobs.slice(startIndex, endIndex);
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: pageData,
+ totalElements: filteredJobs.length,
+ totalPages: Math.ceil(filteredJobs.length / size),
+ size: parseInt(size),
+ number: parseInt(page)
+ }
+ });
+ });
+
+ // 创建合成任务
+ router.post(API.createSynthesisJobUsingPost, (req, res) => {
+ const { templateId } = req.body;
+ const template = synthesisTemplateList.find(t => t.id === templateId);
+
+ const newJob = {
+ ...synthesisJobItem(),
+ ...req.body,
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ templateId,
+ template: template ? {
+ id: template.id,
+ name: template.name,
+ type: template.type
+ } : null,
+ status: "PENDING",
+ progress: 0,
+ generatedCount: 0,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString()
+ };
+ synthesisJobList.push(newJob);
+
+ res.status(201).send({
+ code: "0",
+ msg: "Synthesis job created successfully",
+ data: newJob
+ });
+ });
+
+ // 获取合成任务详情
+ router.get(API.querySynthesisJobByIdUsingGet, (req, res) => {
+ const { jobId } = req.params;
+ const job = synthesisJobList.find((j) => j.id === jobId);
+
+ if (job) {
+ const template = synthesisTemplateList.find(t => t.id === job.templateId);
+ const jobDetail = {
+ ...job,
+ template: template || null
+ };
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: jobDetail
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Synthesis job not found",
+ data: null
+ });
+ }
+ });
+
+ // 删除合成任务
+ router.delete(API.deleteSynthesisJobByIdUsingDelete, (req, res) => {
+ const { jobId } = req.params;
+ const index = synthesisJobList.findIndex((j) => j.id === jobId);
+
+ if (index !== -1) {
+ synthesisJobList.splice(index, 1);
+ res.send({
+ code: "0",
+ msg: "Synthesis job deleted successfully",
+ data: null
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Synthesis job not found",
+ data: null
+ });
+ }
+ });
+
+ // 执行合成任务
+ router.post(API.executeSynthesisJobUsingPost, (req, res) => {
+ const { jobId } = req.params;
+ const job = synthesisJobList.find((j) => j.id === jobId);
+
+ if (job) {
+ job.status = "RUNNING";
+ job.startTime = new Date().toISOString();
+ job.updatedAt = new Date().toISOString();
+
+ // 模拟异步执行
+ setTimeout(() => {
+ job.status = Mock.Random.pick(["COMPLETED", "FAILED"]);
+ job.progress = job.status === "COMPLETED" ? 100 : Mock.Random.float(10, 90, 2, 2);
+ job.generatedCount = Math.floor((job.progress / 100) * job.targetCount);
+ job.endTime = new Date().toISOString();
+ }, Mock.Random.integer(2000, 5000));
+
+ res.send({
+ code: "0",
+ msg: "Synthesis job execution started",
+ data: {
+ executionId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ status: "RUNNING",
+ message: "Job execution started successfully"
+ }
+ });
+ } else {
+ res.status(404).send({
+ code: "1",
+ msg: "Synthesis job not found",
+ data: null
+ });
+ }
+ });
+
+ // 指令调优数据合成
+ router.post(API.instructionTuningUsingPost, (req, res) => {
+ const { baseInstructions, targetDomain, count, modelConfig, parameters } = req.body;
+
+ const jobId = Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, "");
+ const generatedInstructions = new Array(count).fill(null).map(() => generatedInstructionItem());
+
+ const statistics = {
+ totalGenerated: count,
+ averageQuality: Mock.Random.float(0.8, 0.95, 2, 2),
+ diversityScore: Mock.Random.float(0.7, 0.9, 2, 2)
+ };
+
+ res.send({
+ code: "0",
+ msg: "Instruction tuning completed successfully",
+ data: {
+ jobId,
+ generatedInstructions,
+ statistics
+ }
+ });
+ });
+
+ // COT蒸馏数据合成
+ router.post(API.cotDistillationUsingPost, (req, res) => {
+ const { sourceModel, targetFormat, examples, parameters } = req.body;
+
+ const jobId = Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, "");
+ const processedCount = examples.length;
+ const successfulCount = Math.floor(processedCount * Mock.Random.float(0.85, 0.98, 2, 2));
+
+ const distilledData = new Array(successfulCount).fill(null).map(() => distilledCOTDataItem());
+
+ const statistics = {
+ totalProcessed: processedCount,
+ successfulDistilled: successfulCount,
+ averageConfidence: Mock.Random.float(0.8, 0.95, 2, 2)
+ };
+
+ res.send({
+ code: "0",
+ msg: "COT distillation completed successfully",
+ data: {
+ jobId,
+ distilledData,
+ statistics
+ }
+ });
+ });
+
+ // 获取合成任务统计信息
+ router.get("/api/v1/synthesis/statistics", (req, res) => {
+ const statistics = {
+ totalJobs: synthesisJobList.length,
+ completedJobs: synthesisJobList.filter(j => j.status === "COMPLETED").length,
+ runningJobs: synthesisJobList.filter(j => j.status === "RUNNING").length,
+ failedJobs: synthesisJobList.filter(j => j.status === "FAILED").length,
+ totalGenerated: synthesisJobList.reduce((sum, job) => sum + job.generatedCount, 0),
+ averageQuality: Mock.Random.float(0.8, 0.95, 2, 2),
+ templateTypeDistribution: {
+ "INSTRUCTION_TUNING": synthesisTemplateList.filter(t => t.type === "INSTRUCTION_TUNING").length,
+ "COT_DISTILLATION": synthesisTemplateList.filter(t => t.type === "COT_DISTILLATION").length,
+ "DIALOGUE_GENERATION": synthesisTemplateList.filter(t => t.type === "DIALOGUE_GENERATION").length,
+ "TEXT_AUGMENTATION": synthesisTemplateList.filter(t => t.type === "TEXT_AUGMENTATION").length,
+ "MULTIMODAL_SYNTHESIS": synthesisTemplateList.filter(t => t.type === "MULTIMODAL_SYNTHESIS").length,
+ "CUSTOM": synthesisTemplateList.filter(t => t.type === "CUSTOM").length
+ },
+ dailyGeneration: new Array(7).fill(null).map((_, index) => ({
+ date: Mock.Random.date("yyyy-MM-dd"),
+ count: Mock.Random.integer(100, 5000)
+ }))
+ };
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: statistics
+ });
+ });
+
+ // 批量操作
+ router.post("/api/v1/synthesis/jobs/batch", (req, res) => {
+ const { action, jobIds } = req.body;
+
+ let successCount = 0;
+ let failedCount = 0;
+
+ jobIds.forEach(jobId => {
+ const job = synthesisJobList.find(j => j.id === jobId);
+ if (job) {
+ switch(action) {
+ case "DELETE":
+ const index = synthesisJobList.findIndex(j => j.id === jobId);
+ synthesisJobList.splice(index, 1);
+ successCount++;
+ break;
+ case "START":
+ job.status = "RUNNING";
+ job.startTime = new Date().toISOString();
+ successCount++;
+ break;
+ case "STOP":
+ job.status = "CANCELLED";
+ job.endTime = new Date().toISOString();
+ successCount++;
+ break;
+ }
+ } else {
+ failedCount++;
+ }
+ });
+
+ res.send({
+ code: "0",
+ msg: `Batch ${action.toLowerCase()} completed`,
+ data: {
+ total: jobIds.length,
+ success: successCount,
+ failed: failedCount
+ }
+ });
+ });
};
\ No newline at end of file
diff --git a/frontend/src/mock/mock-seed/knowledge-base.cjs b/frontend/src/mock/mock-seed/knowledge-base.cjs
index 9323ce7d4..28f9164eb 100644
--- a/frontend/src/mock/mock-seed/knowledge-base.cjs
+++ b/frontend/src/mock/mock-seed/knowledge-base.cjs
@@ -1,176 +1,176 @@
-const Mock = require("mockjs");
-const API = require("../mock-apis.cjs");
-
-// 知识库数据
-function KnowledgeBaseItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.ctitle(5, 15),
- description: Mock.Random.csentence(10, 30),
- createdBy: Mock.Random.cname(),
- updatedBy: Mock.Random.cname(),
- embeddingModel: Mock.Random.pick([
- "text-embedding-ada-002",
- "text-embedding-3-small",
- "text-embedding-3-large",
- ]),
- chatModel: Mock.Random.pick(["gpt-3.5-turbo", "gpt-4", "gpt-4-32k"]),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- };
-}
-
-const knowledgeBaseList = new Array(50).fill(null).map(KnowledgeBaseItem);
-
-function fileItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- createdBy: Mock.Random.cname(),
- updatedBy: Mock.Random.cname(),
- knowledgeBaseId: Mock.Random.pick(knowledgeBaseList).id,
- fileId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- fileName: Mock.Random.ctitle(5, 15),
- chunkCount: Mock.Random.integer(1, 100),
- metadata: {},
- status: Mock.Random.pick([
- "UNPROCESSED",
- "PROCESSING",
- "PROCESSED",
- "PROCESS_FAILED",
- ]),
- };
-}
-
-const fileList = new Array(20).fill(null).map(fileItem);
-
-module.exports = function (router) {
- // 获取知识库列表
- router.post(API.queryKnowledgeBasesUsingPost, (req, res) => {
- const { page = 0, size, keyword } = req.body;
- let filteredList = knowledgeBaseList;
- if (keyword) {
- filteredList = knowledgeBaseList.filter(
- (kb) => kb.name.includes(keyword) || kb.description.includes(keyword)
- );
- }
- const start = page * size;
- const end = start + size;
- const totalElements = filteredList.length;
- const paginatedList = filteredList.slice(start, end);
- res.send({
- code: "0",
- msg: "Success",
- data: {
- totalElements,
- page,
- size,
- content: paginatedList,
- },
- });
- });
-
- // 创建知识库
- router.post(API.createKnowledgeBaseUsingPost, (req, res) => {
- const item = KnowledgeBaseItem();
- knowledgeBaseList.unshift(item);
- res.status(201).send(item);
- });
-
- // 获取知识库详情
- router.get(API.queryKnowledgeBaseByIdUsingGet, (req, res) => {
- const id = req.params.baseId;
- const item =
- knowledgeBaseList.find((kb) => kb.id === id) || KnowledgeBaseItem();
- res.send({
- code: "0",
- msg: "Success",
- data: item,
- });
- });
-
- // 更新知识库
- router.put(API.updateKnowledgeBaseByIdUsingPut, (req, res) => {
- const id = req.params.baseId;
- const idx = knowledgeBaseList.findIndex((kb) => kb.id === id);
- if (idx >= 0) {
- knowledgeBaseList[idx] = { ...knowledgeBaseList[idx], ...req.body };
- res.status(201).send(knowledgeBaseList[idx]);
- } else {
- res.status(404).send({ message: "Not found" });
- }
- });
-
- // 删除知识库
- router.delete(API.deleteKnowledgeBaseByIdUsingDelete, (req, res) => {
- const id = req.params.baseId;
- const idx = knowledgeBaseList.findIndex((kb) => kb.id === id);
- if (idx >= 0) {
- knowledgeBaseList.splice(idx, 1);
- res.status(201).send({ success: true });
- } else {
- res.status(404).send({ message: "Not found" });
- }
- });
-
- // 添加文件到知识库
- router.post(API.addKnowledgeBaseFilesUsingPost, (req, res) => {
- const file = Mock.mock({
- id: "@guid",
- name: "@ctitle(5,15)",
- size: "@integer(1000,1000000)",
- status: "uploaded",
- createdAt: "@datetime",
- });
- res.status(201).send(file);
- });
-
- // 获取知识生成文件详情
- router.get(API.queryKnowledgeBaseFilesGet, (req, res) => {
- const { keyword, page, size } = req.query;
- let filteredList = fileList;
- if (keyword) {
- filteredList = fileList.filter((file) => file.fileName.includes(keyword));
- }
- const start = page * size;
- const end = start + size;
- const totalElements = filteredList.length;
- const paginatedList = filteredList.slice(start, end);
- res.send({
- code: "0",
- msg: "Success",
- data: {
- totalElements,
- page,
- size,
- content: paginatedList,
- },
- });
- });
-
- router.get(API.queryKnowledgeBaseFilesByIdUsingGet, (req, res) => {
- const { baseId, fileId } = req.params;
- const item =
- fileList.find(
- (file) => file.knowledgeBaseId === baseId && file.id === fileId
- ) || fileItem();
- res.send({
- code: "0",
- msg: "Success",
- data: item,
- });
- });
-
- // 删除知识生成文件
- router.delete(API.deleteKnowledgeBaseTaskByIdUsingDelete, (req, res) => {
- const { id } = req.params;
- const idx = fileList.findIndex((file) => file.id === id);
- if (idx >= 0) {
- fileList.splice(idx, 1);
- res.status(200).send({ success: true });
- return;
- }
- res.status(404).send({ message: "Not found" });
- });
-};
+const Mock = require("mockjs");
+const API = require("../mock-apis.cjs");
+
+// 知识库数据
+function KnowledgeBaseItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.ctitle(5, 15),
+ description: Mock.Random.csentence(10, 30),
+ createdBy: Mock.Random.cname(),
+ updatedBy: Mock.Random.cname(),
+ embeddingModel: Mock.Random.pick([
+ "text-embedding-ada-002",
+ "text-embedding-3-small",
+ "text-embedding-3-large",
+ ]),
+ chatModel: Mock.Random.pick(["gpt-3.5-turbo", "gpt-4", "gpt-4-32k"]),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ };
+}
+
+const knowledgeBaseList = new Array(50).fill(null).map(KnowledgeBaseItem);
+
+function fileItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ createdBy: Mock.Random.cname(),
+ updatedBy: Mock.Random.cname(),
+ knowledgeBaseId: Mock.Random.pick(knowledgeBaseList).id,
+ fileId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ fileName: Mock.Random.ctitle(5, 15),
+ chunkCount: Mock.Random.integer(1, 100),
+ metadata: {},
+ status: Mock.Random.pick([
+ "UNPROCESSED",
+ "PROCESSING",
+ "PROCESSED",
+ "PROCESS_FAILED",
+ ]),
+ };
+}
+
+const fileList = new Array(20).fill(null).map(fileItem);
+
+module.exports = function (router) {
+ // 获取知识库列表
+ router.post(API.queryKnowledgeBasesUsingPost, (req, res) => {
+ const { page = 0, size, keyword } = req.body;
+ let filteredList = knowledgeBaseList;
+ if (keyword) {
+ filteredList = knowledgeBaseList.filter(
+ (kb) => kb.name.includes(keyword) || kb.description.includes(keyword)
+ );
+ }
+ const start = page * size;
+ const end = start + size;
+ const totalElements = filteredList.length;
+ const paginatedList = filteredList.slice(start, end);
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ totalElements,
+ page,
+ size,
+ content: paginatedList,
+ },
+ });
+ });
+
+ // 创建知识库
+ router.post(API.createKnowledgeBaseUsingPost, (req, res) => {
+ const item = KnowledgeBaseItem();
+ knowledgeBaseList.unshift(item);
+ res.status(201).send(item);
+ });
+
+ // 获取知识库详情
+ router.get(API.queryKnowledgeBaseByIdUsingGet, (req, res) => {
+ const id = req.params.baseId;
+ const item =
+ knowledgeBaseList.find((kb) => kb.id === id) || KnowledgeBaseItem();
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: item,
+ });
+ });
+
+ // 更新知识库
+ router.put(API.updateKnowledgeBaseByIdUsingPut, (req, res) => {
+ const id = req.params.baseId;
+ const idx = knowledgeBaseList.findIndex((kb) => kb.id === id);
+ if (idx >= 0) {
+ knowledgeBaseList[idx] = { ...knowledgeBaseList[idx], ...req.body };
+ res.status(201).send(knowledgeBaseList[idx]);
+ } else {
+ res.status(404).send({ message: "Not found" });
+ }
+ });
+
+ // 删除知识库
+ router.delete(API.deleteKnowledgeBaseByIdUsingDelete, (req, res) => {
+ const id = req.params.baseId;
+ const idx = knowledgeBaseList.findIndex((kb) => kb.id === id);
+ if (idx >= 0) {
+ knowledgeBaseList.splice(idx, 1);
+ res.status(201).send({ success: true });
+ } else {
+ res.status(404).send({ message: "Not found" });
+ }
+ });
+
+ // 添加文件到知识库
+ router.post(API.addKnowledgeBaseFilesUsingPost, (req, res) => {
+ const file = Mock.mock({
+ id: "@guid",
+ name: "@ctitle(5,15)",
+ size: "@integer(1000,1000000)",
+ status: "uploaded",
+ createdAt: "@datetime",
+ });
+ res.status(201).send(file);
+ });
+
+ // 获取知识生成文件详情
+ router.get(API.queryKnowledgeBaseFilesGet, (req, res) => {
+ const { keyword, page, size } = req.query;
+ let filteredList = fileList;
+ if (keyword) {
+ filteredList = fileList.filter((file) => file.fileName.includes(keyword));
+ }
+ const start = page * size;
+ const end = start + size;
+ const totalElements = filteredList.length;
+ const paginatedList = filteredList.slice(start, end);
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ totalElements,
+ page,
+ size,
+ content: paginatedList,
+ },
+ });
+ });
+
+ router.get(API.queryKnowledgeBaseFilesByIdUsingGet, (req, res) => {
+ const { baseId, fileId } = req.params;
+ const item =
+ fileList.find(
+ (file) => file.knowledgeBaseId === baseId && file.id === fileId
+ ) || fileItem();
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: item,
+ });
+ });
+
+ // 删除知识生成文件
+ router.delete(API.deleteKnowledgeBaseTaskByIdUsingDelete, (req, res) => {
+ const { id } = req.params;
+ const idx = fileList.findIndex((file) => file.id === id);
+ if (idx >= 0) {
+ fileList.splice(idx, 1);
+ res.status(200).send({ success: true });
+ return;
+ }
+ res.status(404).send({ message: "Not found" });
+ });
+};
diff --git a/frontend/src/mock/mock-seed/operator-market.cjs b/frontend/src/mock/mock-seed/operator-market.cjs
index fb6560a26..97c93cb4d 100644
--- a/frontend/src/mock/mock-seed/operator-market.cjs
+++ b/frontend/src/mock/mock-seed/operator-market.cjs
@@ -1,150 +1,150 @@
-const Mock = require("mockjs");
-const API = require("../mock-apis.cjs");
-
-// 算子标签数据
-function labelItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name: Mock.Random.pick([
- "数据清洗",
- "特征选择",
- "分类算法",
- "聚类算法",
- "回归分析",
- "深度神经网络",
- "卷积神经网络",
- "循环神经网络",
- "注意力机制",
- "文本分析",
- "图像处理",
- "语音识别",
- "推荐算法",
- "异常检测",
- "优化算法",
- "集成学习",
- "迁移学习",
- "强化学习",
- "联邦学习",
- ]),
- usageCount: Mock.Random.integer(1, 500),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- };
-}
-
-const labelList = new Array(50).fill(null).map(labelItem);
-
-module.exports = function (router) {
- router.post(API.preUploadOperatorUsingPost, (req, res) => {
- res.status(201).send(Mock.Random.guid());
- });
-
- // 上传切片
- router.post(API.uploadFileChunkUsingPost, (req, res) => {
- // res.status(500).send({ message: "Simulated upload failure" });
- res.status(201).send({ data: "success" });
- });
-
- // 取消上传
- router.put(API.cancelUploadOperatorUsingPut, (req, res) => {
- res.status(201).send({ data: "success" });
- });
-
- router.post(API.uploadOperatorUsingPost, (req, res) => {
- res.status(201).send({
- code: "0",
- msg: "Upload successful",
- data: {
- operatorId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- // 其他返回数据
- },
- });
- });
-
- // 获取算子标签列表
- router.get(API.queryLabelsUsingGet, (req, res) => {
- const { page = 0, size = 20, keyword = "" } = req.query;
-
- let filteredLabels = labelList;
-
- if (keyword) {
- filteredLabels = labelList.filter((label) =>
- label.name.toLowerCase().includes(keyword.toLowerCase())
- );
- }
-
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = filteredLabels.slice(startIndex, endIndex);
-
- res.send({
- code: "0",
- msg: "Success",
- data: {
- content: pageData,
- totalElements: filteredLabels.length,
- totalPages: Math.ceil(filteredLabels.length / size),
- size: parseInt(size),
- number: parseInt(page),
- },
- });
- });
-
- // 创建标签
- router.post(API.createLabelUsingPost, (req, res) => {
- const { name } = req.body;
-
- const newLabel = {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- name,
- usageCount: 0,
- createdAt: new Date().toISOString(),
- };
-
- labelList.push(newLabel);
-
- res.status(201).send({
- code: "0",
- msg: "Label created successfully",
- data: newLabel,
- });
- });
-
- // 批量删除标签
- router.delete(API.deleteLabelsUsingDelete, (req, res) => {
- const labelIds = req.body; // 数组形式的标签ID列表
-
- let deletedCount = 0;
- labelIds.forEach((labelId) => {
- const index = labelList.findIndex((label) => label.id === labelId);
- if (index !== -1) {
- labelList.splice(index, 1);
- deletedCount++;
- }
- });
-
- res.status(204).send();
- });
-
- // 更新标签
- router.put(API.updateLabelByIdUsingPut, (req, res) => {
- const { id } = req.params;
- const updates = req.body; // 数组形式的更新数据
-
- updates.forEach((update) => {
- const index = labelList.findIndex((label) => label.id === update.id);
- if (index !== -1) {
- labelList[index] = {
- ...labelList[index],
- ...update,
- updatedAt: new Date().toISOString(),
- };
- }
- });
-
- res.send({
- code: "0",
- msg: "Labels updated successfully",
- data: null,
- });
- });
-};
+const Mock = require("mockjs");
+const API = require("../mock-apis.cjs");
+
+// 算子标签数据
+function labelItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name: Mock.Random.pick([
+ "数据清洗",
+ "特征选择",
+ "分类算法",
+ "聚类算法",
+ "回归分析",
+ "深度神经网络",
+ "卷积神经网络",
+ "循环神经网络",
+ "注意力机制",
+ "文本分析",
+ "图像处理",
+ "语音识别",
+ "推荐算法",
+ "异常检测",
+ "优化算法",
+ "集成学习",
+ "迁移学习",
+ "强化学习",
+ "联邦学习",
+ ]),
+ usageCount: Mock.Random.integer(1, 500),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ };
+}
+
+const labelList = new Array(50).fill(null).map(labelItem);
+
+module.exports = function (router) {
+ router.post(API.preUploadOperatorUsingPost, (req, res) => {
+ res.status(201).send(Mock.Random.guid());
+ });
+
+ // 上传切片
+ router.post(API.uploadFileChunkUsingPost, (req, res) => {
+ // res.status(500).send({ message: "Simulated upload failure" });
+ res.status(201).send({ data: "success" });
+ });
+
+ // 取消上传
+ router.put(API.cancelUploadOperatorUsingPut, (req, res) => {
+ res.status(201).send({ data: "success" });
+ });
+
+ router.post(API.uploadOperatorUsingPost, (req, res) => {
+ res.status(201).send({
+ code: "0",
+ msg: "Upload successful",
+ data: {
+ operatorId: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ // 其他返回数据
+ },
+ });
+ });
+
+ // 获取算子标签列表
+ router.get(API.queryLabelsUsingGet, (req, res) => {
+ const { page = 0, size = 20, keyword = "" } = req.query;
+
+ let filteredLabels = labelList;
+
+ if (keyword) {
+ filteredLabels = labelList.filter((label) =>
+ label.name.toLowerCase().includes(keyword.toLowerCase())
+ );
+ }
+
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = filteredLabels.slice(startIndex, endIndex);
+
+ res.send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: pageData,
+ totalElements: filteredLabels.length,
+ totalPages: Math.ceil(filteredLabels.length / size),
+ size: parseInt(size),
+ number: parseInt(page),
+ },
+ });
+ });
+
+ // 创建标签
+ router.post(API.createLabelUsingPost, (req, res) => {
+ const { name } = req.body;
+
+ const newLabel = {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ name,
+ usageCount: 0,
+ createdAt: new Date().toISOString(),
+ };
+
+ labelList.push(newLabel);
+
+ res.status(201).send({
+ code: "0",
+ msg: "Label created successfully",
+ data: newLabel,
+ });
+ });
+
+ // 批量删除标签
+ router.delete(API.deleteLabelsUsingDelete, (req, res) => {
+ const labelIds = req.body; // 数组形式的标签ID列表
+
+ let deletedCount = 0;
+ labelIds.forEach((labelId) => {
+ const index = labelList.findIndex((label) => label.id === labelId);
+ if (index !== -1) {
+ labelList.splice(index, 1);
+ deletedCount++;
+ }
+ });
+
+ res.status(204).send();
+ });
+
+ // 更新标签
+ router.put(API.updateLabelByIdUsingPut, (req, res) => {
+ const { id } = req.params;
+ const updates = req.body; // 数组形式的更新数据
+
+ updates.forEach((update) => {
+ const index = labelList.findIndex((label) => label.id === update.id);
+ if (index !== -1) {
+ labelList[index] = {
+ ...labelList[index],
+ ...update,
+ updatedAt: new Date().toISOString(),
+ };
+ }
+ });
+
+ res.send({
+ code: "0",
+ msg: "Labels updated successfully",
+ data: null,
+ });
+ });
+};
diff --git a/frontend/src/mock/mock-seed/settings.cjs b/frontend/src/mock/mock-seed/settings.cjs
index 8bcca2fdd..886a0b345 100644
--- a/frontend/src/mock/mock-seed/settings.cjs
+++ b/frontend/src/mock/mock-seed/settings.cjs
@@ -1,177 +1,177 @@
-const Mock = require("mockjs");
-const API = require("../mock-apis.cjs");
-
-// 算子标签数据
-function ModelItem() {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- modelName: Mock.Random.pick([
- "数据清洗",
- "特征选择",
- "分类算法",
- "聚类算法",
- "回归分析",
- "深度神经网络",
- "卷积神经网络",
- "循环神经网络",
- "注意力机制",
- "文本分析",
- "图像处理",
- "语音识别",
- "推荐算法",
- "异常检测",
- "优化算法",
- "集成学习",
- "迁移学习",
- "强化学习",
- "联邦学习",
- ]),
- provider: Mock.Random.pick([
- "OpenAI",
- "Anthropic",
- "Cohere",
- "AI21 Labs",
- "Hugging Face",
- "Google Cloud AI",
- "Microsoft Azure AI",
- "Amazon Web Services AI",
- "IBM Watson",
- "Alibaba Cloud AI",
- ]),
- type: Mock.Random.pick(["CHAT", "EMBEDDING"]),
- usageCount: Mock.Random.integer(1, 500),
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- };
-}
-
-const modelList = new Array(50).fill(null).map(ModelItem);
-
-function ProviderItem(provider) {
- return {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- provider,
- baseUrl: Mock.Random.url("https") + "/v1/models",
- createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
- };
-}
-
-const ProviderList = [
- "OpenAI",
- "Anthropic",
- "Cohere",
- "AI21 Labs",
- "Hugging Face",
- "Google Cloud AI",
- "Microsoft Azure AI",
- "Amazon Web Services AI",
- "IBM Watson",
- "Alibaba Cloud AI",
-].map(ProviderItem);
-
-module.exports = function (router) {
- // 获取模型列表
- router.get(API.queryModelsUsingGet, (req, res) => {
- const {
- page = 0,
- size = 20,
- keyword = "",
- provider = "",
- type = "",
- } = req.query;
-
- let filteredModels = modelList;
-
- if (keyword) {
- filteredModels = modelList.filter((model) =>
- model.modelName.toLowerCase().includes(keyword.toLowerCase())
- );
- }
- if (provider && provider !== "all") {
- filteredModels = filteredModels.filter(
- (model) => model.provider === provider
- );
- }
- if (type && type !== "all") {
- filteredModels = filteredModels.filter((model) => model.type === type);
- }
-
- const startIndex = page * size;
- const endIndex = startIndex + parseInt(size);
- const pageData = filteredModels.slice(startIndex, endIndex);
-
- res.status(201).send({
- code: "0",
- msg: "Success",
- data: {
- content: pageData,
- totalElements: filteredModels.length,
- totalPages: Math.ceil(filteredModels.length / size),
- size: parseInt(size),
- number: parseInt(page),
- },
- });
- });
-
- // 获取模型提供商列表
- router.get(API.queryProvidersUsingGet, (req, res) => {
- res.status(201).send({
- code: "0",
- msg: "success",
- data: ProviderList,
- });
- });
-
- // 创建模型
- router.post(API.createModelUsingPost, (req, res) => {
- const { ...modelData } = req.body;
- const newModel = {
- id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
- ...modelData,
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString(),
- };
- modelList.unshift(newModel);
- res.status(201).send({
- code: "0",
- msg: "success",
- data: newModel,
- });
- });
-
- // 删除模型
- router.delete(API.deleteModelUsingDelete, (req, res) => {
- const { id } = req.params;
-
- const index = modelList.findIndex((model) => model.id === id);
- if (index !== -1) {
- modelList.splice(index, 1);
- }
-
- res.status(204).send({
- code: "0",
- msg: "success",
- data: null,
- });
- });
-
- // 更新模型
- router.put(API.updateModelUsingPut, (req, res) => {
- const { id, ...update } = req.params;
-
- const index = modelList.findIndex((model) => model.id === id);
- if (index !== -1) {
- modelList[index] = {
- ...modelList[index],
- ...update,
- updatedAt: new Date().toISOString(),
- };
- }
-
- res.status(201).send({
- code: "0",
- msg: "success",
- data: null,
- });
- });
-};
+const Mock = require("mockjs");
+const API = require("../mock-apis.cjs");
+
+// 算子标签数据
+function ModelItem() {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ modelName: Mock.Random.pick([
+ "数据清洗",
+ "特征选择",
+ "分类算法",
+ "聚类算法",
+ "回归分析",
+ "深度神经网络",
+ "卷积神经网络",
+ "循环神经网络",
+ "注意力机制",
+ "文本分析",
+ "图像处理",
+ "语音识别",
+ "推荐算法",
+ "异常检测",
+ "优化算法",
+ "集成学习",
+ "迁移学习",
+ "强化学习",
+ "联邦学习",
+ ]),
+ provider: Mock.Random.pick([
+ "OpenAI",
+ "Anthropic",
+ "Cohere",
+ "AI21 Labs",
+ "Hugging Face",
+ "Google Cloud AI",
+ "Microsoft Azure AI",
+ "Amazon Web Services AI",
+ "IBM Watson",
+ "Alibaba Cloud AI",
+ ]),
+ type: Mock.Random.pick(["CHAT", "EMBEDDING"]),
+ usageCount: Mock.Random.integer(1, 500),
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ };
+}
+
+const modelList = new Array(50).fill(null).map(ModelItem);
+
+function ProviderItem(provider) {
+ return {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ provider,
+ baseUrl: Mock.Random.url("https") + "/v1/models",
+ createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
+ };
+}
+
+const ProviderList = [
+ "OpenAI",
+ "Anthropic",
+ "Cohere",
+ "AI21 Labs",
+ "Hugging Face",
+ "Google Cloud AI",
+ "Microsoft Azure AI",
+ "Amazon Web Services AI",
+ "IBM Watson",
+ "Alibaba Cloud AI",
+].map(ProviderItem);
+
+module.exports = function (router) {
+ // 获取模型列表
+ router.get(API.queryModelsUsingGet, (req, res) => {
+ const {
+ page = 0,
+ size = 20,
+ keyword = "",
+ provider = "",
+ type = "",
+ } = req.query;
+
+ let filteredModels = modelList;
+
+ if (keyword) {
+ filteredModels = modelList.filter((model) =>
+ model.modelName.toLowerCase().includes(keyword.toLowerCase())
+ );
+ }
+ if (provider && provider !== "all") {
+ filteredModels = filteredModels.filter(
+ (model) => model.provider === provider
+ );
+ }
+ if (type && type !== "all") {
+ filteredModels = filteredModels.filter((model) => model.type === type);
+ }
+
+ const startIndex = page * size;
+ const endIndex = startIndex + parseInt(size);
+ const pageData = filteredModels.slice(startIndex, endIndex);
+
+ res.status(201).send({
+ code: "0",
+ msg: "Success",
+ data: {
+ content: pageData,
+ totalElements: filteredModels.length,
+ totalPages: Math.ceil(filteredModels.length / size),
+ size: parseInt(size),
+ number: parseInt(page),
+ },
+ });
+ });
+
+ // 获取模型提供商列表
+ router.get(API.queryProvidersUsingGet, (req, res) => {
+ res.status(201).send({
+ code: "0",
+ msg: "success",
+ data: ProviderList,
+ });
+ });
+
+ // 创建模型
+ router.post(API.createModelUsingPost, (req, res) => {
+ const { ...modelData } = req.body;
+ const newModel = {
+ id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
+ ...modelData,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ };
+ modelList.unshift(newModel);
+ res.status(201).send({
+ code: "0",
+ msg: "success",
+ data: newModel,
+ });
+ });
+
+ // 删除模型
+ router.delete(API.deleteModelUsingDelete, (req, res) => {
+ const { id } = req.params;
+
+ const index = modelList.findIndex((model) => model.id === id);
+ if (index !== -1) {
+ modelList.splice(index, 1);
+ }
+
+ res.status(204).send({
+ code: "0",
+ msg: "success",
+ data: null,
+ });
+ });
+
+ // 更新模型
+ router.put(API.updateModelUsingPut, (req, res) => {
+ const { id, ...update } = req.params;
+
+ const index = modelList.findIndex((model) => model.id === id);
+ if (index !== -1) {
+ modelList[index] = {
+ ...modelList[index],
+ ...update,
+ updatedAt: new Date().toISOString(),
+ };
+ }
+
+ res.status(201).send({
+ code: "0",
+ msg: "success",
+ data: null,
+ });
+ });
+};
diff --git a/frontend/src/mock/mock.cjs b/frontend/src/mock/mock.cjs
index eb23a8a3d..1e50cd7ab 100644
--- a/frontend/src/mock/mock.cjs
+++ b/frontend/src/mock/mock.cjs
@@ -1,58 +1,58 @@
-const express = require('express');
-const fs = require('fs-extra');
-const path = require('path');
-const bodyParser = require('body-parser');
-const { genExpressSession } = require('./mock-core/session-helper.cjs');
-const {
- setHeader,
- sendJSON,
- strongMatch,
- errorHandle,
-} = require('./mock-middleware/index.cjs');
-
-
-const { loadAllMockModules } = require('./mock-core/module-loader.cjs');
-const { log } = require('./mock-core/util.cjs');
-
-const app = express();
-const router = express.Router();
-
-const argv = require('minimist')(process.argv.slice(2));
-const deployUrl = argv['deploy-url'] || '/';
-const deployPath = argv['deploy-path'] || '/';
-const port = argv.port || 8002;
-const env = argv.env || 'development';
-
-// app静态文件实际目录
-const deployAppPath = path.join(__dirname, deployPath);
-preStartCheck(deployAppPath);
-
-app.use(setHeader);
-
-// 提供静态文件服务
-app.use(deployUrl, express.static(deployAppPath));
-app.use(bodyParser.json({limit: '1mb'}));
-app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }));
-app.use(sendJSON);
-app.use(strongMatch);
-app.use(genExpressSession());
-
-const mockDir = path.join(__dirname, '/mock-seed');
-loadAllMockModules(router, mockDir);
-app.use(deployUrl, router);
-app.use(errorHandle);
-
-app.get('/', (req, res) => {
- res.sendFile('default response', { root: deployAppPath });
-});
-
-app.listen(port, function() {
- log(`Mock server is running at http://localhost:${port}${deployUrl} in ${env} mode`);
-})
-
-function preStartCheck(deployAppPath) {
- if(!fs.existsSync(deployAppPath)) {
- log(`Error: The path ${deployAppPath} does not exist. Please build the frontend application first.`, 'error');
- process.exit(1);
- }
+const express = require('express');
+const fs = require('fs-extra');
+const path = require('path');
+const bodyParser = require('body-parser');
+const { genExpressSession } = require('./mock-core/session-helper.cjs');
+const {
+ setHeader,
+ sendJSON,
+ strongMatch,
+ errorHandle,
+} = require('./mock-middleware/index.cjs');
+
+
+const { loadAllMockModules } = require('./mock-core/module-loader.cjs');
+const { log } = require('./mock-core/util.cjs');
+
+const app = express();
+const router = express.Router();
+
+const argv = require('minimist')(process.argv.slice(2));
+const deployUrl = argv['deploy-url'] || '/';
+const deployPath = argv['deploy-path'] || '/';
+const port = argv.port || 8002;
+const env = argv.env || 'development';
+
+// app静态文件实际目录
+const deployAppPath = path.join(__dirname, deployPath);
+preStartCheck(deployAppPath);
+
+app.use(setHeader);
+
+// 提供静态文件服务
+app.use(deployUrl, express.static(deployAppPath));
+app.use(bodyParser.json({limit: '1mb'}));
+app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }));
+app.use(sendJSON);
+app.use(strongMatch);
+app.use(genExpressSession());
+
+const mockDir = path.join(__dirname, '/mock-seed');
+loadAllMockModules(router, mockDir);
+app.use(deployUrl, router);
+app.use(errorHandle);
+
+app.get('/', (req, res) => {
+ res.sendFile('default response', { root: deployAppPath });
+});
+
+app.listen(port, function() {
+ log(`Mock server is running at http://localhost:${port}${deployUrl} in ${env} mode`);
+})
+
+function preStartCheck(deployAppPath) {
+ if(!fs.existsSync(deployAppPath)) {
+ log(`Error: The path ${deployAppPath} does not exist. Please build the frontend application first.`, 'error');
+ process.exit(1);
+ }
}
\ No newline at end of file
diff --git a/frontend/src/mock/nodemon.json b/frontend/src/mock/nodemon.json
index eb9ea0c96..90e2cc486 100644
--- a/frontend/src/mock/nodemon.json
+++ b/frontend/src/mock/nodemon.json
@@ -1,22 +1,22 @@
-{
- "restartable": "rs",
- "ignore": [
- ".git",
- "node_modules/**/node_modules",
- "dist",
- "build",
- "*.test.js",
- "*.spec.js"
- ],
- "verbose": true,
- "watch": ["*.cjs"],
- "exec": "node --inspect=0.0.0.0:9229 mock.cjs",
- "ext": "js,cjs,json",
- "execMap": {
- "js": "node --harmony"
- },
- "env": {
- "NODE_ENV": "development"
- },
- "signal": "SIGTERM"
-}
+{
+ "restartable": "rs",
+ "ignore": [
+ ".git",
+ "node_modules/**/node_modules",
+ "dist",
+ "build",
+ "*.test.js",
+ "*.spec.js"
+ ],
+ "verbose": true,
+ "watch": ["*.cjs"],
+ "exec": "node --inspect=0.0.0.0:9229 mock.cjs",
+ "ext": "js,cjs,json",
+ "execMap": {
+ "js": "node --harmony"
+ },
+ "env": {
+ "NODE_ENV": "development"
+ },
+ "signal": "SIGTERM"
+}
diff --git a/frontend/src/mock/synthesis.tsx b/frontend/src/mock/synthesis.tsx
index 5de7ae362..cc961207a 100644
--- a/frontend/src/mock/synthesis.tsx
+++ b/frontend/src/mock/synthesis.tsx
@@ -1,209 +1,209 @@
-// Add mock files data
-export const mockFiles = [
- { id: "file1", name: "dataset_part_001.jsonl", size: "2.5MB", type: "JSONL" },
- { id: "file2", name: "dataset_part_002.jsonl", size: "2.3MB", type: "JSONL" },
- { id: "file3", name: "dataset_part_003.jsonl", size: "2.7MB", type: "JSONL" },
- { id: "file4", name: "training_data.txt", size: "1.8MB", type: "TXT" },
- { id: "file5", name: "validation_set.csv", size: "856KB", type: "CSV" },
- { id: "file6", name: "test_samples.json", size: "1.2MB", type: "JSON" },
- { id: "file7", name: "raw_text_001.txt", size: "3.1MB", type: "TXT" },
- { id: "file8", name: "raw_text_002.txt", size: "2.9MB", type: "TXT" },
-];
-
-export const mockSynthesisTasks: SynthesisTask[] = [
- {
- id: 1,
- name: "文字生成问答对_判断题",
- type: "qa",
- status: "completed",
- progress: 100,
- sourceDataset: "orig_20250724_64082",
- targetCount: 1000,
- generatedCount: 1000,
- createdAt: "2025-01-20",
- template: "判断题生成模板",
- estimatedTime: "已完成",
- quality: 95,
- },
- {
- id: 2,
- name: "知识蒸馏数据集",
- type: "distillation",
- status: "running",
- progress: 65,
- sourceDataset: "teacher_model_outputs",
- targetCount: 5000,
- generatedCount: 3250,
- createdAt: "2025-01-22",
- template: "蒸馏模板v2",
- estimatedTime: "剩余 15 分钟",
- quality: 88,
- },
- {
- id: 3,
- name: "多模态对话生成",
- type: "multimodal",
- status: "failed",
- progress: 25,
- sourceDataset: "image_text_pairs",
- targetCount: 2000,
- generatedCount: 500,
- createdAt: "2025-01-23",
- template: "多模态对话模板",
- errorMessage: "模型API调用失败,请检查配置",
- },
- {
- id: 4,
- name: "金融问答数据生成",
- type: "qa",
- status: "pending",
- progress: 0,
- sourceDataset: "financial_qa_dataset",
- targetCount: 800,
- generatedCount: 0,
- createdAt: "2025-01-24",
- template: "金融问答模板",
- estimatedTime: "等待开始",
- quality: 0,
- },
- {
- id: 5,
- name: "医疗文本蒸馏",
- type: "distillation",
- status: "paused",
- progress: 45,
- sourceDataset: "medical_corpus",
- targetCount: 3000,
- generatedCount: 1350,
- createdAt: "2025-01-21",
- template: "医疗蒸馏模板",
- estimatedTime: "已暂停",
- quality: 92,
- },
-];
-
-export const mockTemplates: Template[] = [
- {
- id: 1,
- name: "判断题生成模板",
- type: "preset",
- category: "问答对生成",
- prompt: `根据给定的文本内容,生成一个判断题。
-
-文本内容:{text}
-
-请按照以下格式生成:
-1. 判断题:[基于文本内容的判断题]
-2. 答案:[对/错]
-3. 解释:[简要解释为什么这个答案是正确的]
-
-要求:
-- 判断题应该基于文本的核心内容
-- 答案必须明确且有依据
-- 解释要简洁清晰`,
- variables: ["text"],
- description: "根据文本内容生成判断题,适用于教育和培训场景",
- usageCount: 156,
- lastUsed: "2025-01-20",
- quality: 95,
- },
- {
- id: 2,
- name: "选择题生成模板",
- type: "preset",
- category: "问答对生成",
- prompt: `基于以下文本,创建一个多选题:
-
-{text}
-
-请按照以下格式生成:
-问题:[基于文本的问题]
-A. [选项A]
-B. [选项B]
-C. [选项C]
-D. [选项D]
-正确答案:[A/B/C/D]
-解析:[详细解释]
-
-要求:
-- 问题要有一定难度
-- 选项要有迷惑性
-- 正确答案要有充分依据`,
- variables: ["text"],
- description: "生成多选题的标准模板,适用于考试和评估",
- usageCount: 89,
- lastUsed: "2025-01-19",
- quality: 92,
- },
- {
- id: 3,
- name: "知识蒸馏模板",
- type: "preset",
- category: "蒸馏数据集",
- prompt: `作为学生模型,学习教师模型的输出:
-
-输入:{input}
-教师输出:{teacher_output}
-
-请模仿教师模型的推理过程和输出格式,生成相似质量的回答。
-
-要求:
-- 保持教师模型的推理逻辑
-- 输出格式要一致
-- 质量要接近教师模型水平`,
- variables: ["input", "teacher_output"],
- description: "用于知识蒸馏的模板,帮助小模型学习大模型的能力",
- usageCount: 234,
- lastUsed: "2025-01-22",
- quality: 88,
- },
- {
- id: 4,
- name: "金融问答模板",
- type: "custom",
- category: "问答对生成",
- prompt: `基于金融领域知识,生成专业问答对:
-
-参考内容:{content}
-
-生成格式:
-问题:[专业的金融问题]
-答案:[准确的专业回答]
-关键词:[相关金融术语]
-
-要求:
-- 问题具有实用性
-- 答案准确专业
-- 符合金融行业标准`,
- variables: ["content"],
- description: "专门用于金融领域的问答对生成",
- usageCount: 45,
- lastUsed: "2025-01-18",
- quality: 89,
- },
- {
- id: 5,
- name: "医疗蒸馏模板",
- type: "custom",
- category: "蒸馏数据集",
- prompt: `医疗知识蒸馏模板:
-
-原始医疗文本:{medical_text}
-专家标注:{expert_annotation}
-
-生成医疗知识点:
-1. 核心概念:[提取关键医疗概念]
-2. 临床意义:[说明临床应用价值]
-3. 注意事项:[重要提醒和禁忌]
-
-要求:
-- 确保医疗信息准确性
-- 遵循医疗伦理规范
-- 适合医学教育使用`,
- variables: ["medical_text", "expert_annotation"],
- description: "医疗领域专用的知识蒸馏模板",
- usageCount: 67,
- lastUsed: "2025-01-21",
- quality: 94,
- },
-];
+// Add mock files data
+export const mockFiles = [
+ { id: "file1", name: "dataset_part_001.jsonl", size: "2.5MB", type: "JSONL" },
+ { id: "file2", name: "dataset_part_002.jsonl", size: "2.3MB", type: "JSONL" },
+ { id: "file3", name: "dataset_part_003.jsonl", size: "2.7MB", type: "JSONL" },
+ { id: "file4", name: "training_data.txt", size: "1.8MB", type: "TXT" },
+ { id: "file5", name: "validation_set.csv", size: "856KB", type: "CSV" },
+ { id: "file6", name: "test_samples.json", size: "1.2MB", type: "JSON" },
+ { id: "file7", name: "raw_text_001.txt", size: "3.1MB", type: "TXT" },
+ { id: "file8", name: "raw_text_002.txt", size: "2.9MB", type: "TXT" },
+];
+
+export const mockSynthesisTasks: SynthesisTask[] = [
+ {
+ id: 1,
+ name: "文字生成问答对_判断题",
+ type: "qa",
+ status: "completed",
+ progress: 100,
+ sourceDataset: "orig_20250724_64082",
+ targetCount: 1000,
+ generatedCount: 1000,
+ createdAt: "2025-01-20",
+ template: "判断题生成模板",
+ estimatedTime: "已完成",
+ quality: 95,
+ },
+ {
+ id: 2,
+ name: "知识蒸馏数据集",
+ type: "distillation",
+ status: "running",
+ progress: 65,
+ sourceDataset: "teacher_model_outputs",
+ targetCount: 5000,
+ generatedCount: 3250,
+ createdAt: "2025-01-22",
+ template: "蒸馏模板v2",
+ estimatedTime: "剩余 15 分钟",
+ quality: 88,
+ },
+ {
+ id: 3,
+ name: "多模态对话生成",
+ type: "multimodal",
+ status: "failed",
+ progress: 25,
+ sourceDataset: "image_text_pairs",
+ targetCount: 2000,
+ generatedCount: 500,
+ createdAt: "2025-01-23",
+ template: "多模态对话模板",
+ errorMessage: "模型API调用失败,请检查配置",
+ },
+ {
+ id: 4,
+ name: "金融问答数据生成",
+ type: "qa",
+ status: "pending",
+ progress: 0,
+ sourceDataset: "financial_qa_dataset",
+ targetCount: 800,
+ generatedCount: 0,
+ createdAt: "2025-01-24",
+ template: "金融问答模板",
+ estimatedTime: "等待开始",
+ quality: 0,
+ },
+ {
+ id: 5,
+ name: "医疗文本蒸馏",
+ type: "distillation",
+ status: "paused",
+ progress: 45,
+ sourceDataset: "medical_corpus",
+ targetCount: 3000,
+ generatedCount: 1350,
+ createdAt: "2025-01-21",
+ template: "医疗蒸馏模板",
+ estimatedTime: "已暂停",
+ quality: 92,
+ },
+];
+
+export const mockTemplates: Template[] = [
+ {
+ id: 1,
+ name: "判断题生成模板",
+ type: "preset",
+ category: "问答对生成",
+ prompt: `根据给定的文本内容,生成一个判断题。
+
+文本内容:{text}
+
+请按照以下格式生成:
+1. 判断题:[基于文本内容的判断题]
+2. 答案:[对/错]
+3. 解释:[简要解释为什么这个答案是正确的]
+
+要求:
+- 判断题应该基于文本的核心内容
+- 答案必须明确且有依据
+- 解释要简洁清晰`,
+ variables: ["text"],
+ description: "根据文本内容生成判断题,适用于教育和培训场景",
+ usageCount: 156,
+ lastUsed: "2025-01-20",
+ quality: 95,
+ },
+ {
+ id: 2,
+ name: "选择题生成模板",
+ type: "preset",
+ category: "问答对生成",
+ prompt: `基于以下文本,创建一个多选题:
+
+{text}
+
+请按照以下格式生成:
+问题:[基于文本的问题]
+A. [选项A]
+B. [选项B]
+C. [选项C]
+D. [选项D]
+正确答案:[A/B/C/D]
+解析:[详细解释]
+
+要求:
+- 问题要有一定难度
+- 选项要有迷惑性
+- 正确答案要有充分依据`,
+ variables: ["text"],
+ description: "生成多选题的标准模板,适用于考试和评估",
+ usageCount: 89,
+ lastUsed: "2025-01-19",
+ quality: 92,
+ },
+ {
+ id: 3,
+ name: "知识蒸馏模板",
+ type: "preset",
+ category: "蒸馏数据集",
+ prompt: `作为学生模型,学习教师模型的输出:
+
+输入:{input}
+教师输出:{teacher_output}
+
+请模仿教师模型的推理过程和输出格式,生成相似质量的回答。
+
+要求:
+- 保持教师模型的推理逻辑
+- 输出格式要一致
+- 质量要接近教师模型水平`,
+ variables: ["input", "teacher_output"],
+ description: "用于知识蒸馏的模板,帮助小模型学习大模型的能力",
+ usageCount: 234,
+ lastUsed: "2025-01-22",
+ quality: 88,
+ },
+ {
+ id: 4,
+ name: "金融问答模板",
+ type: "custom",
+ category: "问答对生成",
+ prompt: `基于金融领域知识,生成专业问答对:
+
+参考内容:{content}
+
+生成格式:
+问题:[专业的金融问题]
+答案:[准确的专业回答]
+关键词:[相关金融术语]
+
+要求:
+- 问题具有实用性
+- 答案准确专业
+- 符合金融行业标准`,
+ variables: ["content"],
+ description: "专门用于金融领域的问答对生成",
+ usageCount: 45,
+ lastUsed: "2025-01-18",
+ quality: 89,
+ },
+ {
+ id: 5,
+ name: "医疗蒸馏模板",
+ type: "custom",
+ category: "蒸馏数据集",
+ prompt: `医疗知识蒸馏模板:
+
+原始医疗文本:{medical_text}
+专家标注:{expert_annotation}
+
+生成医疗知识点:
+1. 核心概念:[提取关键医疗概念]
+2. 临床意义:[说明临床应用价值]
+3. 注意事项:[重要提醒和禁忌]
+
+要求:
+- 确保医疗信息准确性
+- 遵循医疗伦理规范
+- 适合医学教育使用`,
+ variables: ["medical_text", "expert_annotation"],
+ description: "医疗领域专用的知识蒸馏模板",
+ usageCount: 67,
+ lastUsed: "2025-01-21",
+ quality: 94,
+ },
+];
diff --git a/frontend/src/pages/Agent/Agent.tsx b/frontend/src/pages/Agent/Agent.tsx
index 53861c633..94df683d5 100644
--- a/frontend/src/pages/Agent/Agent.tsx
+++ b/frontend/src/pages/Agent/Agent.tsx
@@ -1,480 +1,480 @@
-import type React from "react";
-
-import { useState, useRef, useEffect } from "react";
-import { Card, Input, Button, Badge } from "antd";
-import { HomeOutlined } from "@ant-design/icons";
-import {
- MessageSquare,
- Send,
- Bot,
- User,
- Sparkles,
- Database,
- BarChart3,
- Settings,
- Zap,
- CheckCircle,
- Clock,
- Download,
- ArrowLeft,
-} from "lucide-react";
-import { useNavigate } from "react-router";
-import DevelopmentInProgress from "@/components/DevelopmentInProgress";
-
-interface Message {
- id: string;
- type: "user" | "assistant";
- content: string;
- timestamp: Date;
- actions?: Array<{
- type:
- | "create_dataset"
- | "run_analysis"
- | "start_synthesis"
- | "export_report";
- label: string;
- data?: any;
- }>;
- status?: "pending" | "completed" | "error";
-}
-
-interface QuickAction {
- id: string;
- label: string;
- icon: any;
- prompt: string;
- category: string;
-}
-
-const quickActions: QuickAction[] = [
- {
- id: "create_dataset",
- label: "创建数据集",
- icon: Database,
- prompt: "帮我创建一个新的数据集",
- category: "数据管理",
- },
- {
- id: "analyze_quality",
- label: "质量分析",
- icon: BarChart3,
- prompt: "分析我的数据集质量",
- category: "数据评估",
- },
- {
- id: "start_synthesis",
- label: "数据合成",
- icon: Sparkles,
- prompt: "启动数据合成任务",
- category: "数据合成",
- },
- {
- id: "process_data",
- label: "数据清洗",
- icon: Settings,
- prompt: "对数据集进行预处理",
- category: "数据清洗",
- },
- {
- id: "export_report",
- label: "导出报告",
- icon: Download,
- prompt: "导出最新的分析报告",
- category: "报告导出",
- },
- {
- id: "check_status",
- label: "查看状态",
- icon: Clock,
- prompt: "查看所有任务的运行状态",
- category: "状态查询",
- },
-];
-
-const mockResponses = {
- 创建数据集: {
- content:
- "我来帮您创建一个新的数据集。请告诉我以下信息:\n\n1. 数据集名称\n2. 数据类型(图像、文本、问答对等)\n3. 预期数据量\n4. 数据来源\n\n您也可以直接说出您的需求,我会为您推荐最适合的配置。",
- actions: [
- { type: "create_dataset", label: "开始创建", data: { step: "config" } },
- ],
- },
- 质量分析: {
- content:
- "正在为您分析数据集质量...\n\n📊 **分析结果概览:**\n- 图像分类数据集:质量分 92/100\n- 问答对数据集:质量分 87/100\n- 多模态数据集:质量分 78/100\n\n🔍 **发现的主要问题:**\n- 23个重复图像\n- 156个格式不正确的问答对\n- 78个图文不匹配项\n\n💡 **改进建议:**\n- 建议进行去重处理\n- 优化问答对格式\n- 重新标注图文匹配项",
- actions: [
- {
- type: "run_analysis",
- label: "查看详细报告",
- data: { type: "detailed" },
- },
- ],
- },
- 数据合成: {
- content:
- "我可以帮您启动数据合成任务。目前支持以下合成类型:\n\n🖼️ **图像数据合成**\n- 数据增强(旋转、翻转、亮度调整)\n- 风格迁移\n- GAN生成\n\n📝 **文本数据合成**\n- 同义词替换\n- 回译增强\n- GPT生成\n\n❓ **问答对合成**\n- 基于知识库生成\n- 模板变换\n- 多轮对话生成\n\n请告诉我您需要合成什么类型的数据,以及目标数量。",
- actions: [
- {
- type: "start_synthesis",
- label: "配置合成任务",
- data: { step: "config" },
- },
- ],
- },
- 导出报告: {
- content:
- "正在为您准备最新的分析报告...\n\n📋 **可用报告:**\n- 数据质量评估报告(PDF)\n- 数据分布统计报告(Excel)\n- 模型性能评估报告(PDF)\n- 偏见检测报告(PDF)\n- 综合分析报告(PDF + Excel)\n\n✅ 报告已生成完成,您可以选择下载格式。",
- actions: [
- { type: "export_report", label: "下载报告", data: { format: "pdf" } },
- ],
- },
- 查看状态: {
- content:
- "📊 **当前任务状态概览:**\n\n🟢 **运行中的任务:**\n- 问答对生成任务:65% 完成\n- 图像质量分析:运行中\n- 知识库构建:等待中\n\n✅ **已完成的任务:**\n- 图像分类数据集创建:已完成\n- PDF文档提取:已完成\n- 训练集配比任务:已完成\n\n⚠️ **需要关注的任务:**\n- 多模态数据合成:暂停(需要用户确认参数)\n\n所有任务运行正常,预计2小时内全部完成。",
- actions: [],
- },
-};
-
-export default function AgentPage() {
- return ;
- const navigate = useNavigate();
- const [messages, setMessages] = useState([
- {
- id: "welcome",
- type: "assistant",
- content:
- "👋 您好!我是 Data Agent,您的AI数据助手。\n\n我可以帮您:\n• 创建和管理数据集\n• 分析数据质量\n• 启动处理任务\n• 生成分析报告\n• 回答数据相关问题\n\n请告诉我您需要什么帮助,或者点击下方的快捷操作开始。",
- timestamp: new Date(),
- },
- ]);
- const [inputValue, setInputValue] = useState("");
- const [isTyping, setIsTyping] = useState(false);
- const messagesEndRef = useRef(null);
- const inputRef = useRef(null);
-
- const scrollToBottom = () => {
- messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
- };
-
- useEffect(() => {
- scrollToBottom();
- }, [messages]);
-
- const handleSendMessage = async (content: string) => {
- if (!content.trim()) return;
-
- const userMessage: Message = {
- id: Date.now().toString(),
- type: "user",
- content: content.trim(),
- timestamp: new Date(),
- };
-
- setMessages((prev) => [...prev, userMessage]);
- setInputValue("");
- setIsTyping(true);
-
- // 模拟AI响应
- setTimeout(() => {
- const response = generateResponse(content);
- const assistantMessage: Message = {
- id: (Date.now() + 1).toString(),
- type: "assistant",
- content: response.content,
- timestamp: new Date(),
- actions: response.actions,
- };
-
- setMessages((prev) => [...prev, assistantMessage]);
- setIsTyping(false);
- }, 1500);
- };
-
- const generateResponse = (
- input: string
- ): { content: string; actions?: any[] } => {
- const lowerInput = input.toLowerCase();
-
- if (lowerInput.includes("创建") && lowerInput.includes("数据集")) {
- return mockResponses["创建数据集"];
- } else if (lowerInput.includes("质量") || lowerInput.includes("分析")) {
- return mockResponses["质量分析"];
- } else if (lowerInput.includes("合成") || lowerInput.includes("生成")) {
- return mockResponses["数据合成"];
- } else if (lowerInput.includes("导出") || lowerInput.includes("报告")) {
- return mockResponses["导出报告"];
- } else if (lowerInput.includes("状态") || lowerInput.includes("任务")) {
- return mockResponses["查看状态"];
- } else if (lowerInput.includes("你好") || lowerInput.includes("帮助")) {
- return {
- content:
- "很高兴为您服务!我是专门为数据集管理设计的AI助手。\n\n我的主要能力包括:\n\n🔧 **数据集操作**\n- 创建、导入、导出数据集\n- 数据预处理和清洗\n- 批量操作和自动化\n\n📊 **智能分析**\n- 数据质量评估\n- 分布统计分析\n- 性能和偏见检测\n\n🤖 **AI增强**\n- 智能数据合成\n- 自动标注建议\n- 知识库构建\n\n请告诉我您的具体需求,我会为您提供最合适的解决方案!",
- };
- } else {
- return {
- content: `我理解您想要「${input}」。让我为您分析一下...\n\n基于您的需求,我建议:\n\n1. 首先确认具体的操作目标\n2. 选择合适的数据集和参数\n3. 执行相应的处理流程\n\n您可以提供更多详细信息,或者选择下方的快捷操作来开始。如果需要帮助,请说"帮助"获取完整功能列表。`,
- actions: [
- { type: "run_analysis", label: "开始分析", data: { query: input } },
- ],
- };
- }
- };
-
- const handleQuickAction = (action: QuickAction) => {
- handleSendMessage(action.prompt);
- };
-
- const handleActionClick = (action: any) => {
- const actionMessage: Message = {
- id: Date.now().toString(),
- type: "assistant",
- content: `✅ 正在执行「${action.label}」...\n\n操作已启动,您可以在相应的功能模块中查看详细进度。`,
- timestamp: new Date(),
- status: "completed",
- };
- setMessages((prev) => [...prev, actionMessage]);
- };
-
- const handleKeyPress = (e: React.KeyboardEvent) => {
- if (e.key === "Enter" && !e.shiftKey) {
- e.preventDefault();
- handleSendMessage(inputValue);
- }
- };
-
- const formatMessage = (content: string) => {
- return content.split("\n").map((line, index) => (
-
- {line ||
}
-
- ));
- };
-
- const onBack = () => {
- navigate("/");
- };
-
- return (
-
-
- {/* Header */}
-
-
-
-
-
-
-
-
-
Data Agent
-
- AI驱动的智能数据助手,通过对话完成复杂数据操作
-
-
-
-
}
- onClick={onBack}
- className="bg-white/10 border-white/20 text-white hover:bg-white/20 hover:border-white/30"
- >
- 返回首页
-
-
-
-
-
-
-
- {/* Chat Area */}
-
-
-
-
- {/* Messages */}
-
-
- {messages.map((message) => (
-
- {message.type === "assistant" && (
-
-
-
- )}
-
-
- {formatMessage(message.content)}
-
- {message.actions && message.actions.length > 0 && (
-
- {message.actions.map((action, index) => (
- handleActionClick(action)}
- >
- {action.label}
-
- ))}
-
- )}
-
- {message.timestamp.toLocaleTimeString()}
-
-
- {message.type === "user" && (
-
-
-
- )}
-
- ))}
- {isTyping && (
-
- )}
-
-
-
-
- {/* Input Area */}
-
-
- setInputValue(e.target.value)}
- onKeyDown={handleKeyPress}
- placeholder="输入您的需求,例如:创建一个图像分类数据集..."
- disabled={isTyping}
- />
- handleSendMessage(inputValue)}
- disabled={!inputValue.trim() || isTyping}
- className="bg-gradient-to-r from-purple-400 to-pink-400 border-none hover:from-purple-500 hover:to-pink-500"
- >
-
-
-
-
-
-
-
-
- {/* Quick Actions Sidebar */}
-
-
-
-
快捷操作
-
- 点击快速开始常用操作
-
-
-
- {quickActions.map((action) => (
-
handleQuickAction(action)}
- >
-
-
-
- ))}
-
-
-
-
-
- 系统状态
-
-
-
-
- AI服务正常
-
-
-
- 3个任务运行中
-
-
-
- 12个数据集就绪
-
-
-
- 响应时间: 0.8s
-
-
-
-
-
-
- 使用提示
-
-
-
💡 您可以用自然语言描述需求
-
🔍 支持复杂的多步骤操作
-
📊 可以询问数据统计和分析
-
⚡ 使用快捷操作提高效率
-
-
-
-
-
- }
- onClick={onBack}
- >
- 返回主应用
-
-
-
-
-
-
-
-
- );
-}
+import type React from "react";
+
+import { useState, useRef, useEffect } from "react";
+import { Card, Input, Button, Badge } from "antd";
+import { HomeOutlined } from "@ant-design/icons";
+import {
+ MessageSquare,
+ Send,
+ Bot,
+ User,
+ Sparkles,
+ Database,
+ BarChart3,
+ Settings,
+ Zap,
+ CheckCircle,
+ Clock,
+ Download,
+ ArrowLeft,
+} from "lucide-react";
+import { useNavigate } from "react-router";
+import DevelopmentInProgress from "@/components/DevelopmentInProgress";
+
+interface Message {
+ id: string;
+ type: "user" | "assistant";
+ content: string;
+ timestamp: Date;
+ actions?: Array<{
+ type:
+ | "create_dataset"
+ | "run_analysis"
+ | "start_synthesis"
+ | "export_report";
+ label: string;
+ data?: any;
+ }>;
+ status?: "pending" | "completed" | "error";
+}
+
+interface QuickAction {
+ id: string;
+ label: string;
+ icon: any;
+ prompt: string;
+ category: string;
+}
+
+const quickActions: QuickAction[] = [
+ {
+ id: "create_dataset",
+ label: "创建数据集",
+ icon: Database,
+ prompt: "帮我创建一个新的数据集",
+ category: "数据管理",
+ },
+ {
+ id: "analyze_quality",
+ label: "质量分析",
+ icon: BarChart3,
+ prompt: "分析我的数据集质量",
+ category: "数据评估",
+ },
+ {
+ id: "start_synthesis",
+ label: "数据合成",
+ icon: Sparkles,
+ prompt: "启动数据合成任务",
+ category: "数据合成",
+ },
+ {
+ id: "process_data",
+ label: "数据清洗",
+ icon: Settings,
+ prompt: "对数据集进行预处理",
+ category: "数据清洗",
+ },
+ {
+ id: "export_report",
+ label: "导出报告",
+ icon: Download,
+ prompt: "导出最新的分析报告",
+ category: "报告导出",
+ },
+ {
+ id: "check_status",
+ label: "查看状态",
+ icon: Clock,
+ prompt: "查看所有任务的运行状态",
+ category: "状态查询",
+ },
+];
+
+const mockResponses = {
+ 创建数据集: {
+ content:
+ "我来帮您创建一个新的数据集。请告诉我以下信息:\n\n1. 数据集名称\n2. 数据类型(图像、文本、问答对等)\n3. 预期数据量\n4. 数据来源\n\n您也可以直接说出您的需求,我会为您推荐最适合的配置。",
+ actions: [
+ { type: "create_dataset", label: "开始创建", data: { step: "config" } },
+ ],
+ },
+ 质量分析: {
+ content:
+ "正在为您分析数据集质量...\n\n📊 **分析结果概览:**\n- 图像分类数据集:质量分 92/100\n- 问答对数据集:质量分 87/100\n- 多模态数据集:质量分 78/100\n\n🔍 **发现的主要问题:**\n- 23个重复图像\n- 156个格式不正确的问答对\n- 78个图文不匹配项\n\n💡 **改进建议:**\n- 建议进行去重处理\n- 优化问答对格式\n- 重新标注图文匹配项",
+ actions: [
+ {
+ type: "run_analysis",
+ label: "查看详细报告",
+ data: { type: "detailed" },
+ },
+ ],
+ },
+ 数据合成: {
+ content:
+ "我可以帮您启动数据合成任务。目前支持以下合成类型:\n\n🖼️ **图像数据合成**\n- 数据增强(旋转、翻转、亮度调整)\n- 风格迁移\n- GAN生成\n\n📝 **文本数据合成**\n- 同义词替换\n- 回译增强\n- GPT生成\n\n❓ **问答对合成**\n- 基于知识库生成\n- 模板变换\n- 多轮对话生成\n\n请告诉我您需要合成什么类型的数据,以及目标数量。",
+ actions: [
+ {
+ type: "start_synthesis",
+ label: "配置合成任务",
+ data: { step: "config" },
+ },
+ ],
+ },
+ 导出报告: {
+ content:
+ "正在为您准备最新的分析报告...\n\n📋 **可用报告:**\n- 数据质量评估报告(PDF)\n- 数据分布统计报告(Excel)\n- 模型性能评估报告(PDF)\n- 偏见检测报告(PDF)\n- 综合分析报告(PDF + Excel)\n\n✅ 报告已生成完成,您可以选择下载格式。",
+ actions: [
+ { type: "export_report", label: "下载报告", data: { format: "pdf" } },
+ ],
+ },
+ 查看状态: {
+ content:
+ "📊 **当前任务状态概览:**\n\n🟢 **运行中的任务:**\n- 问答对生成任务:65% 完成\n- 图像质量分析:运行中\n- 知识库构建:等待中\n\n✅ **已完成的任务:**\n- 图像分类数据集创建:已完成\n- PDF文档提取:已完成\n- 训练集配比任务:已完成\n\n⚠️ **需要关注的任务:**\n- 多模态数据合成:暂停(需要用户确认参数)\n\n所有任务运行正常,预计2小时内全部完成。",
+ actions: [],
+ },
+};
+
+export default function AgentPage() {
+ return ;
+ const navigate = useNavigate();
+ const [messages, setMessages] = useState([
+ {
+ id: "welcome",
+ type: "assistant",
+ content:
+ "👋 您好!我是 Data Agent,您的AI数据助手。\n\n我可以帮您:\n• 创建和管理数据集\n• 分析数据质量\n• 启动处理任务\n• 生成分析报告\n• 回答数据相关问题\n\n请告诉我您需要什么帮助,或者点击下方的快捷操作开始。",
+ timestamp: new Date(),
+ },
+ ]);
+ const [inputValue, setInputValue] = useState("");
+ const [isTyping, setIsTyping] = useState(false);
+ const messagesEndRef = useRef(null);
+ const inputRef = useRef(null);
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
+ };
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [messages]);
+
+ const handleSendMessage = async (content: string) => {
+ if (!content.trim()) return;
+
+ const userMessage: Message = {
+ id: Date.now().toString(),
+ type: "user",
+ content: content.trim(),
+ timestamp: new Date(),
+ };
+
+ setMessages((prev) => [...prev, userMessage]);
+ setInputValue("");
+ setIsTyping(true);
+
+ // 模拟AI响应
+ setTimeout(() => {
+ const response = generateResponse(content);
+ const assistantMessage: Message = {
+ id: (Date.now() + 1).toString(),
+ type: "assistant",
+ content: response.content,
+ timestamp: new Date(),
+ actions: response.actions,
+ };
+
+ setMessages((prev) => [...prev, assistantMessage]);
+ setIsTyping(false);
+ }, 1500);
+ };
+
+ const generateResponse = (
+ input: string
+ ): { content: string; actions?: any[] } => {
+ const lowerInput = input.toLowerCase();
+
+ if (lowerInput.includes("创建") && lowerInput.includes("数据集")) {
+ return mockResponses["创建数据集"];
+ } else if (lowerInput.includes("质量") || lowerInput.includes("分析")) {
+ return mockResponses["质量分析"];
+ } else if (lowerInput.includes("合成") || lowerInput.includes("生成")) {
+ return mockResponses["数据合成"];
+ } else if (lowerInput.includes("导出") || lowerInput.includes("报告")) {
+ return mockResponses["导出报告"];
+ } else if (lowerInput.includes("状态") || lowerInput.includes("任务")) {
+ return mockResponses["查看状态"];
+ } else if (lowerInput.includes("你好") || lowerInput.includes("帮助")) {
+ return {
+ content:
+ "很高兴为您服务!我是专门为数据集管理设计的AI助手。\n\n我的主要能力包括:\n\n🔧 **数据集操作**\n- 创建、导入、导出数据集\n- 数据预处理和清洗\n- 批量操作和自动化\n\n📊 **智能分析**\n- 数据质量评估\n- 分布统计分析\n- 性能和偏见检测\n\n🤖 **AI增强**\n- 智能数据合成\n- 自动标注建议\n- 知识库构建\n\n请告诉我您的具体需求,我会为您提供最合适的解决方案!",
+ };
+ } else {
+ return {
+ content: `我理解您想要「${input}」。让我为您分析一下...\n\n基于您的需求,我建议:\n\n1. 首先确认具体的操作目标\n2. 选择合适的数据集和参数\n3. 执行相应的处理流程\n\n您可以提供更多详细信息,或者选择下方的快捷操作来开始。如果需要帮助,请说"帮助"获取完整功能列表。`,
+ actions: [
+ { type: "run_analysis", label: "开始分析", data: { query: input } },
+ ],
+ };
+ }
+ };
+
+ const handleQuickAction = (action: QuickAction) => {
+ handleSendMessage(action.prompt);
+ };
+
+ const handleActionClick = (action: any) => {
+ const actionMessage: Message = {
+ id: Date.now().toString(),
+ type: "assistant",
+ content: `✅ 正在执行「${action.label}」...\n\n操作已启动,您可以在相应的功能模块中查看详细进度。`,
+ timestamp: new Date(),
+ status: "completed",
+ };
+ setMessages((prev) => [...prev, actionMessage]);
+ };
+
+ const handleKeyPress = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault();
+ handleSendMessage(inputValue);
+ }
+ };
+
+ const formatMessage = (content: string) => {
+ return content.split("\n").map((line, index) => (
+
+ {line ||
}
+
+ ));
+ };
+
+ const onBack = () => {
+ navigate("/");
+ };
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
+
+
+
+
+
Data Agent
+
+ AI驱动的智能数据助手,通过对话完成复杂数据操作
+
+
+
+
}
+ onClick={onBack}
+ className="bg-white/10 border-white/20 text-white hover:bg-white/20 hover:border-white/30"
+ >
+ 返回首页
+
+
+
+
+
+
+
+ {/* Chat Area */}
+
+
+
+
+ {/* Messages */}
+
+
+ {messages.map((message) => (
+
+ {message.type === "assistant" && (
+
+
+
+ )}
+
+
+ {formatMessage(message.content)}
+
+ {message.actions && message.actions.length > 0 && (
+
+ {message.actions.map((action, index) => (
+ handleActionClick(action)}
+ >
+ {action.label}
+
+ ))}
+
+ )}
+
+ {message.timestamp.toLocaleTimeString()}
+
+
+ {message.type === "user" && (
+
+
+
+ )}
+
+ ))}
+ {isTyping && (
+
+ )}
+
+
+
+
+ {/* Input Area */}
+
+
+ setInputValue(e.target.value)}
+ onKeyDown={handleKeyPress}
+ placeholder="输入您的需求,例如:创建一个图像分类数据集..."
+ disabled={isTyping}
+ />
+ handleSendMessage(inputValue)}
+ disabled={!inputValue.trim() || isTyping}
+ className="bg-gradient-to-r from-purple-400 to-pink-400 border-none hover:from-purple-500 hover:to-pink-500"
+ >
+
+
+
+
+
+
+
+
+ {/* Quick Actions Sidebar */}
+
+
+
+
快捷操作
+
+ 点击快速开始常用操作
+
+
+
+ {quickActions.map((action) => (
+
handleQuickAction(action)}
+ >
+
+
+
+ ))}
+
+
+
+
+
+ 系统状态
+
+
+
+
+ AI服务正常
+
+
+
+ 3个任务运行中
+
+
+
+ 12个数据集就绪
+
+
+
+ 响应时间: 0.8s
+
+
+
+
+
+
+ 使用提示
+
+
+
💡 您可以用自然语言描述需求
+
🔍 支持复杂的多步骤操作
+
📊 可以询问数据统计和分析
+
⚡ 使用快捷操作提高效率
+
+
+
+
+
+ }
+ onClick={onBack}
+ >
+ 返回主应用
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/DataAnnotation/Annotate/AnnotationWorkSpace.tsx b/frontend/src/pages/DataAnnotation/Annotate/AnnotationWorkSpace.tsx
index 416640029..fb5910d80 100644
--- a/frontend/src/pages/DataAnnotation/Annotate/AnnotationWorkSpace.tsx
+++ b/frontend/src/pages/DataAnnotation/Annotate/AnnotationWorkSpace.tsx
@@ -1,229 +1,229 @@
-import { useEffect, useState } from "react";
-import { Card, message } from "antd";
-import { Button, Badge, Progress, Checkbox } from "antd";
-import {
- ArrowLeft,
- FileText,
- ImageIcon,
- Video,
- Music,
- Save,
- SkipForward,
- CheckCircle,
- Eye,
- Settings,
-} from "lucide-react";
-import { mockTasks } from "@/mock/annotation";
-import { Outlet, useNavigate } from "react-router";
-
-export default function AnnotationWorkspace() {
- const navigate = useNavigate();
- const [task, setTask] = useState(mockTasks[0]);
-
- const [currentFileIndex, setCurrentFileIndex] = useState(0);
- const [annotationProgress, setAnnotationProgress] = useState({
- completed: task.completedCount,
- skipped: task.skippedCount,
- total: task.totalCount,
- });
-
- const handleSaveAndNext = () => {
- setAnnotationProgress((prev) => ({
- ...prev,
- completed: prev.completed + 1,
- }));
-
- if (currentFileIndex < task.totalCount - 1) {
- setCurrentFileIndex(currentFileIndex + 1);
- }
-
- message({
- title: "标注已保存",
- description: "标注结果已保存,自动跳转到下一个",
- });
- };
-
- const handleSkipAndNext = () => {
- setAnnotationProgress((prev) => ({
- ...prev,
- skipped: prev.skipped + 1,
- }));
-
- if (currentFileIndex < task.totalCount - 1) {
- setCurrentFileIndex(currentFileIndex + 1);
- }
-
- message({
- title: "已跳过",
- description: "已跳过当前项目,自动跳转到下一个",
- });
- };
-
- const getDatasetTypeIcon = (type: string) => {
- switch (type) {
- case "text":
- return ;
- case "image":
- return ;
- case "video":
- return ;
- case "audio":
- return ;
- default:
- return ;
- }
- };
-
- const currentProgress = Math.round(
- ((annotationProgress.completed + annotationProgress.skipped) /
- annotationProgress.total) *
- 100
- );
-
- return (
-
- {/* Header */}
-
-
-
navigate("/data/annotation")}
- icon={}
- >
-
- {getDatasetTypeIcon(task.datasetType)}
- {task.name}
-
-
-
-
- {currentFileIndex + 1} / {task.totalCount}
-
-
-
进度:
-
-
{currentProgress}%
-
-
-
-
- {/* Main Content */}
-
- {/* Annotation Area */}
-
-
-
-
- {/* Right Sidebar - Only show for text and image types */}
- {(task.datasetType === "text" || task.datasetType === "image") && (
-
- {/* Progress Stats */}
-
-
-
- 已完成
-
- {annotationProgress.completed}
-
-
-
- 已跳过
-
- {annotationProgress.skipped}
-
-
-
- 剩余
-
- {annotationProgress.total -
- annotationProgress.completed -
- annotationProgress.skipped}
-
-
-
-
- 总进度
- {currentProgress}%
-
-
-
-
- {/* Quick Actions */}
-
-
- }
- >
- 保存并下一个
-
- }
- >
- 跳过并下一个
-
- }>
- 仅保存
-
- }>
- 预览结果
-
-
-
-
- {/* Navigation */}
-
-
-
- setCurrentFileIndex(currentFileIndex - 1)}
- >
- 上一个
-
- setCurrentFileIndex(currentFileIndex + 1)}
- >
- 下一个
-
-
-
- 当前: {currentFileIndex + 1} / {task.totalCount}
-
-
-
-
- {/* Settings */}
-
-
-
- 自动保存
-
-
-
- 快捷键提示
-
-
-
}>
- 更多设置
-
-
-
-
- )}
-
-
- );
-}
+import { useEffect, useState } from "react";
+import { Card, message } from "antd";
+import { Button, Badge, Progress, Checkbox } from "antd";
+import {
+ ArrowLeft,
+ FileText,
+ ImageIcon,
+ Video,
+ Music,
+ Save,
+ SkipForward,
+ CheckCircle,
+ Eye,
+ Settings,
+} from "lucide-react";
+import { mockTasks } from "@/mock/annotation";
+import { Outlet, useNavigate } from "react-router";
+
+export default function AnnotationWorkspace() {
+ const navigate = useNavigate();
+ const [task, setTask] = useState(mockTasks[0]);
+
+ const [currentFileIndex, setCurrentFileIndex] = useState(0);
+ const [annotationProgress, setAnnotationProgress] = useState({
+ completed: task.completedCount,
+ skipped: task.skippedCount,
+ total: task.totalCount,
+ });
+
+ const handleSaveAndNext = () => {
+ setAnnotationProgress((prev) => ({
+ ...prev,
+ completed: prev.completed + 1,
+ }));
+
+ if (currentFileIndex < task.totalCount - 1) {
+ setCurrentFileIndex(currentFileIndex + 1);
+ }
+
+ message({
+ title: "标注已保存",
+ description: "标注结果已保存,自动跳转到下一个",
+ });
+ };
+
+ const handleSkipAndNext = () => {
+ setAnnotationProgress((prev) => ({
+ ...prev,
+ skipped: prev.skipped + 1,
+ }));
+
+ if (currentFileIndex < task.totalCount - 1) {
+ setCurrentFileIndex(currentFileIndex + 1);
+ }
+
+ message({
+ title: "已跳过",
+ description: "已跳过当前项目,自动跳转到下一个",
+ });
+ };
+
+ const getDatasetTypeIcon = (type: string) => {
+ switch (type) {
+ case "text":
+ return ;
+ case "image":
+ return ;
+ case "video":
+ return ;
+ case "audio":
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const currentProgress = Math.round(
+ ((annotationProgress.completed + annotationProgress.skipped) /
+ annotationProgress.total) *
+ 100
+ );
+
+ return (
+
+ {/* Header */}
+
+
+
navigate("/data/annotation")}
+ icon={}
+ >
+
+ {getDatasetTypeIcon(task.datasetType)}
+ {task.name}
+
+
+
+
+ {currentFileIndex + 1} / {task.totalCount}
+
+
+
进度:
+
+
{currentProgress}%
+
+
+
+
+ {/* Main Content */}
+
+ {/* Annotation Area */}
+
+
+
+
+ {/* Right Sidebar - Only show for text and image types */}
+ {(task.datasetType === "text" || task.datasetType === "image") && (
+
+ {/* Progress Stats */}
+
+
+
+ 已完成
+
+ {annotationProgress.completed}
+
+
+
+ 已跳过
+
+ {annotationProgress.skipped}
+
+
+
+ 剩余
+
+ {annotationProgress.total -
+ annotationProgress.completed -
+ annotationProgress.skipped}
+
+
+
+
+ 总进度
+ {currentProgress}%
+
+
+
+
+ {/* Quick Actions */}
+
+
+ }
+ >
+ 保存并下一个
+
+ }
+ >
+ 跳过并下一个
+
+ }>
+ 仅保存
+
+ }>
+ 预览结果
+
+
+
+
+ {/* Navigation */}
+
+
+
+ setCurrentFileIndex(currentFileIndex - 1)}
+ >
+ 上一个
+
+ setCurrentFileIndex(currentFileIndex + 1)}
+ >
+ 下一个
+
+
+
+ 当前: {currentFileIndex + 1} / {task.totalCount}
+
+
+
+
+ {/* Settings */}
+
+
+
+ 自动保存
+
+
+
+ 快捷键提示
+
+
+
}>
+ 更多设置
+
+
+
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/pages/DataAnnotation/Annotate/components/AudioAnnotation.tsx b/frontend/src/pages/DataAnnotation/Annotate/components/AudioAnnotation.tsx
index 17864627c..9e690c59d 100644
--- a/frontend/src/pages/DataAnnotation/Annotate/components/AudioAnnotation.tsx
+++ b/frontend/src/pages/DataAnnotation/Annotate/components/AudioAnnotation.tsx
@@ -1,713 +1,713 @@
-import { useState, useRef, useEffect } from "react";
-import { Card, Button, Badge, Slider, message } from "antd";
-import {
- Play,
- Pause,
- Square,
- SkipBack,
- SkipForward,
- Volume2,
- VolumeX,
- Scissors,
- Save,
- CheckCircle,
- Trash2,
- Edit,
- Mic,
- AudioWaveformIcon as Waveform,
-} from "lucide-react";
-
-interface AudioSegment {
- id: string;
- startTime: number;
- endTime: number;
- transcription: string;
- label: string;
- confidence?: number;
- speaker?: string;
-}
-
-interface AudioAnnotationWorkspaceProps {
- task: any;
- currentFileIndex: number;
- onSaveAndNext: () => void;
- onSkipAndNext: () => void;
-}
-
-// 模拟音频数据
-const mockAudioFiles = [
- {
- id: "1",
- name: "interview_001.wav",
- url: "/placeholder-audio.mp3", // 这里应该是实际的音频文件URL
- duration: 180, // 3分钟
- segments: [
- {
- id: "1",
- startTime: 0,
- endTime: 15,
- transcription: "你好,欢迎参加今天的访谈。请先介绍一下自己。",
- label: "问题",
- confidence: 0.95,
- speaker: "主持人",
- },
- {
- id: "2",
- startTime: 15,
- endTime: 45,
- transcription:
- "大家好,我是张三,目前在一家科技公司担任产品经理,有五年的工作经验。",
- label: "回答",
- confidence: 0.88,
- speaker: "受访者",
- },
- {
- id: "3",
- startTime: 45,
- endTime: 60,
- transcription: "很好,那么请谈谈你对人工智能发展的看法。",
- label: "问题",
- confidence: 0.92,
- speaker: "主持人",
- },
- ],
- },
-];
-
-// 预定义标签
-const audioLabels = [
- { name: "问题", color: "#3B82F6" },
- { name: "回答", color: "#10B981" },
- { name: "讨论", color: "#F59E0B" },
- { name: "总结", color: "#EF4444" },
- { name: "背景音", color: "#8B5CF6" },
- { name: "其他", color: "#6B7280" },
-];
-
-export default function AudioAnnotationWorkspace({
- task,
- currentFileIndex,
- onSaveAndNext,
- onSkipAndNext,
-}: AudioAnnotationWorkspaceProps) {
- const audioRef = useRef(null);
- const [currentAudio] = useState(mockAudioFiles[0]);
- const [segments, setSegments] = useState(
- currentAudio.segments
- );
- const [isPlaying, setIsPlaying] = useState(false);
- const [currentTime, setCurrentTime] = useState(0);
- const [duration, setDuration] = useState(currentAudio.duration);
- const [volume, setVolume] = useState(1);
- const [isMuted, setIsMuted] = useState(false);
- const [selectedSegment, setSelectedSegment] = useState(null);
- const [isCreatingSegment, setIsCreatingSegment] = useState(false);
- const [newSegmentStart, setNewSegmentStart] = useState(0);
- const [editingSegment, setEditingSegment] = useState(
- null
- );
-
- useEffect(() => {
- const audio = audioRef.current;
- if (!audio) return;
-
- const updateTime = () => setCurrentTime(audio.currentTime);
- const updateDuration = () => setDuration(audio.duration);
- const handleEnded = () => setIsPlaying(false);
-
- audio.addEventListener("timeupdate", updateTime);
- audio.addEventListener("loadedmetadata", updateDuration);
- audio.addEventListener("ended", handleEnded);
-
- return () => {
- audio.removeEventListener("timeupdate", updateTime);
- audio.removeEventListener("loadedmetadata", updateDuration);
- audio.removeEventListener("ended", handleEnded);
- };
- }, []);
-
- const togglePlayPause = () => {
- const audio = audioRef.current;
- if (!audio) return;
-
- if (isPlaying) {
- audio.pause();
- } else {
- audio.play();
- }
- setIsPlaying(!isPlaying);
- };
-
- const handleSeek = (time: number) => {
- const audio = audioRef.current;
- if (!audio) return;
-
- audio.currentTime = time;
- setCurrentTime(time);
- };
-
- const handleVolumeChange = (value: number[]) => {
- const audio = audioRef.current;
- if (!audio) return;
-
- const newVolume = value[0];
- audio.volume = newVolume;
- setVolume(newVolume);
- setIsMuted(newVolume === 0);
- };
-
- const toggleMute = () => {
- const audio = audioRef.current;
- if (!audio) return;
-
- if (isMuted) {
- audio.volume = volume;
- setIsMuted(false);
- } else {
- audio.volume = 0;
- setIsMuted(true);
- }
- };
-
- const startCreatingSegment = () => {
- setIsCreatingSegment(true);
- setNewSegmentStart(currentTime);
- toast({
- title: "开始创建片段",
- description: `片段起始时间: ${formatTime(currentTime)}`,
- });
- };
-
- const finishCreatingSegment = () => {
- if (!isCreatingSegment) return;
-
- const newSegment: AudioSegment = {
- id: Date.now().toString(),
- startTime: newSegmentStart,
- endTime: currentTime,
- transcription: "",
- label: audioLabels[0].name,
- speaker: "",
- };
-
- setSegments([...segments, newSegment]);
- setIsCreatingSegment(false);
- setEditingSegment(newSegment);
-
- toast({
- title: "片段已创建",
- description: `时长: ${formatTime(currentTime - newSegmentStart)}`,
- });
- };
-
- const deleteSegment = (id: string) => {
- setSegments(segments.filter((s) => s.id !== id));
- setSelectedSegment(null);
- toast({
- title: "片段已删除",
- description: "音频片段已被删除",
- });
- };
-
- const updateSegment = (updatedSegment: AudioSegment) => {
- setSegments(
- segments.map((s) => (s.id === updatedSegment.id ? updatedSegment : s))
- );
- setEditingSegment(null);
- toast({
- title: "片段已更新",
- description: "转录内容已保存",
- });
- };
-
- const playSegment = (segment: AudioSegment) => {
- handleSeek(segment.startTime);
- setSelectedSegment(segment.id);
-
- const audio = audioRef.current;
- if (!audio) return;
-
- audio.play();
- setIsPlaying(true);
-
- // 在片段结束时暂停
- const checkEnd = () => {
- if (audio.currentTime >= segment.endTime) {
- audio.pause();
- setIsPlaying(false);
- audio.removeEventListener("timeupdate", checkEnd);
- }
- };
- audio.addEventListener("timeupdate", checkEnd);
- };
-
- const formatTime = (seconds: number) => {
- const mins = Math.floor(seconds / 60);
- const secs = Math.floor(seconds % 60);
- return `${mins.toString().padStart(2, "0")}:${secs
- .toString()
- .padStart(2, "0")}`;
- };
-
- const getSegmentColor = (label: string) => {
- const labelConfig = audioLabels.find((l) => l.name === label);
- return labelConfig?.color || "#6B7280";
- };
-
- return (
-
- {/* Audio Player */}
-
-
- {/* Audio Element */}
-
-
- {/* Player Controls */}
-
-
handleSeek(Math.max(0, currentTime - 10))}
- >
-
-
-
- {isPlaying ? (
-
- ) : (
-
- )}
-
-
handleSeek(Math.min(duration, currentTime + 10))}
- >
-
-
-
-
- {/* Timeline */}
-
-
- {formatTime(currentTime)}
- {formatTime(duration)}
-
-
-
handleSeek(value[0])}
- className="w-full"
- />
- {/* Segment Visualization */}
-
- {segments.map((segment) => {
- const left = (segment.startTime / duration) * 100;
- const width =
- ((segment.endTime - segment.startTime) / duration) * 100;
- return (
-
- );
- })}
-
- {/* Current Creating Segment */}
- {isCreatingSegment && (
-
- )}
-
-
-
- {/* Volume Control */}
-
-
- {isMuted ? (
-
- ) : (
-
- )}
-
-
-
-
- {/* Annotation Controls */}
-
- {isCreatingSegment ? (
-
-
- 完成片段
-
- ) : (
-
-
- 创建片段
-
- )}
-
-
-
-
- {/* Main Content */}
-
- {/* Segments List */}
-
-
-
-
音频片段
- {segments.length} 个片段
-
-
-
-
- {segments.map((segment) => (
-
setSelectedSegment(segment.id)}
- >
-
-
-
-
-
-
- {segment.label}
-
-
-
-
{
- e.stopPropagation();
- playSegment(segment);
- }}
- >
-
-
-
{
- e.stopPropagation();
- setEditingSegment(segment);
- }}
- >
-
-
-
{
- e.stopPropagation();
- deleteSegment(segment.id);
- }}
- >
-
-
-
-
-
- {formatTime(segment.startTime)} -{" "}
- {formatTime(segment.endTime)}
- {segment.speaker && ` | ${segment.speaker}`}
-
-
- {segment.transcription || "未转录"}
-
- {segment.confidence && (
-
-
- 置信度:
-
-
- {(segment.confidence * 100).toFixed(1)}%
-
-
- )}
-
-
-
- ))}
-
-
-
-
-
- {/* Transcription Editor */}
-
- {editingSegment ? (
-
-
-
-
- 编辑转录
-
-
-
-
-
-
-
-
-
-
-
-
-
- setEditingSegment({
- ...editingSegment,
- speaker: e.target.value,
- })
- }
- placeholder="输入说话人名称"
- className="mt-1"
- />
-
-
-
-
-
-
-
- setEditingSegment(null)}
- >
- 取消
-
- updateSegment(editingSegment)}
- className="bg-blue-600 hover:bg-blue-700"
- >
-
- 保存
-
-
-
-
- ) : selectedSegment ? (
-
-
-
-
- 片段详情
-
-
-
- {(() => {
- const segment = segments.find(
- (s) => s.id === selectedSegment
- );
- if (!segment) return null;
-
- return (
-
-
-
-
- 时间范围
-
-
- {formatTime(segment.startTime)} -{" "}
- {formatTime(segment.endTime)}
-
-
-
-
时长
-
- {formatTime(segment.endTime - segment.startTime)}
-
-
-
-
-
-
- {segment.speaker && (
-
-
说话人
-
{segment.speaker}
-
- )}
-
-
-
转录内容
-
- {segment.transcription || "暂无转录内容"}
-
-
-
- {segment.confidence && (
-
-
置信度
-
- {(segment.confidence * 100).toFixed(1)}%
-
-
- )}
-
-
-
playSegment(segment)}
- variant="outline"
- >
-
- 播放片段
-
-
setEditingSegment(segment)}
- variant="outline"
- >
-
- 编辑
-
-
-
- );
- })()}
-
-
- ) : (
-
-
-
-
- 音频标注工作区
-
-
- 选择一个音频片段开始编辑转录内容
-
-
-
• 点击"创建片段"开始标记音频片段
-
• 选择片段进行转录和标注
-
• 使用播放控件精确定位音频位置
-
-
-
- )}
-
-
-
- {/* Bottom Actions */}
-
-
-
- 文件: {currentAudio.name} | 片段: {segments.length} | 总时长:{" "}
- {formatTime(duration)}
-
-
-
- 跳过
-
-
-
- 保存并下一个
-
-
-
-
-
- );
-}
+import { useState, useRef, useEffect } from "react";
+import { Card, Button, Badge, Slider, message } from "antd";
+import {
+ Play,
+ Pause,
+ Square,
+ SkipBack,
+ SkipForward,
+ Volume2,
+ VolumeX,
+ Scissors,
+ Save,
+ CheckCircle,
+ Trash2,
+ Edit,
+ Mic,
+ AudioWaveformIcon as Waveform,
+} from "lucide-react";
+
+interface AudioSegment {
+ id: string;
+ startTime: number;
+ endTime: number;
+ transcription: string;
+ label: string;
+ confidence?: number;
+ speaker?: string;
+}
+
+interface AudioAnnotationWorkspaceProps {
+ task: any;
+ currentFileIndex: number;
+ onSaveAndNext: () => void;
+ onSkipAndNext: () => void;
+}
+
+// 模拟音频数据
+const mockAudioFiles = [
+ {
+ id: "1",
+ name: "interview_001.wav",
+ url: "/placeholder-audio.mp3", // 这里应该是实际的音频文件URL
+ duration: 180, // 3分钟
+ segments: [
+ {
+ id: "1",
+ startTime: 0,
+ endTime: 15,
+ transcription: "你好,欢迎参加今天的访谈。请先介绍一下自己。",
+ label: "问题",
+ confidence: 0.95,
+ speaker: "主持人",
+ },
+ {
+ id: "2",
+ startTime: 15,
+ endTime: 45,
+ transcription:
+ "大家好,我是张三,目前在一家科技公司担任产品经理,有五年的工作经验。",
+ label: "回答",
+ confidence: 0.88,
+ speaker: "受访者",
+ },
+ {
+ id: "3",
+ startTime: 45,
+ endTime: 60,
+ transcription: "很好,那么请谈谈你对人工智能发展的看法。",
+ label: "问题",
+ confidence: 0.92,
+ speaker: "主持人",
+ },
+ ],
+ },
+];
+
+// 预定义标签
+const audioLabels = [
+ { name: "问题", color: "#3B82F6" },
+ { name: "回答", color: "#10B981" },
+ { name: "讨论", color: "#F59E0B" },
+ { name: "总结", color: "#EF4444" },
+ { name: "背景音", color: "#8B5CF6" },
+ { name: "其他", color: "#6B7280" },
+];
+
+export default function AudioAnnotationWorkspace({
+ task,
+ currentFileIndex,
+ onSaveAndNext,
+ onSkipAndNext,
+}: AudioAnnotationWorkspaceProps) {
+ const audioRef = useRef(null);
+ const [currentAudio] = useState(mockAudioFiles[0]);
+ const [segments, setSegments] = useState(
+ currentAudio.segments
+ );
+ const [isPlaying, setIsPlaying] = useState(false);
+ const [currentTime, setCurrentTime] = useState(0);
+ const [duration, setDuration] = useState(currentAudio.duration);
+ const [volume, setVolume] = useState(1);
+ const [isMuted, setIsMuted] = useState(false);
+ const [selectedSegment, setSelectedSegment] = useState(null);
+ const [isCreatingSegment, setIsCreatingSegment] = useState(false);
+ const [newSegmentStart, setNewSegmentStart] = useState(0);
+ const [editingSegment, setEditingSegment] = useState(
+ null
+ );
+
+ useEffect(() => {
+ const audio = audioRef.current;
+ if (!audio) return;
+
+ const updateTime = () => setCurrentTime(audio.currentTime);
+ const updateDuration = () => setDuration(audio.duration);
+ const handleEnded = () => setIsPlaying(false);
+
+ audio.addEventListener("timeupdate", updateTime);
+ audio.addEventListener("loadedmetadata", updateDuration);
+ audio.addEventListener("ended", handleEnded);
+
+ return () => {
+ audio.removeEventListener("timeupdate", updateTime);
+ audio.removeEventListener("loadedmetadata", updateDuration);
+ audio.removeEventListener("ended", handleEnded);
+ };
+ }, []);
+
+ const togglePlayPause = () => {
+ const audio = audioRef.current;
+ if (!audio) return;
+
+ if (isPlaying) {
+ audio.pause();
+ } else {
+ audio.play();
+ }
+ setIsPlaying(!isPlaying);
+ };
+
+ const handleSeek = (time: number) => {
+ const audio = audioRef.current;
+ if (!audio) return;
+
+ audio.currentTime = time;
+ setCurrentTime(time);
+ };
+
+ const handleVolumeChange = (value: number[]) => {
+ const audio = audioRef.current;
+ if (!audio) return;
+
+ const newVolume = value[0];
+ audio.volume = newVolume;
+ setVolume(newVolume);
+ setIsMuted(newVolume === 0);
+ };
+
+ const toggleMute = () => {
+ const audio = audioRef.current;
+ if (!audio) return;
+
+ if (isMuted) {
+ audio.volume = volume;
+ setIsMuted(false);
+ } else {
+ audio.volume = 0;
+ setIsMuted(true);
+ }
+ };
+
+ const startCreatingSegment = () => {
+ setIsCreatingSegment(true);
+ setNewSegmentStart(currentTime);
+ toast({
+ title: "开始创建片段",
+ description: `片段起始时间: ${formatTime(currentTime)}`,
+ });
+ };
+
+ const finishCreatingSegment = () => {
+ if (!isCreatingSegment) return;
+
+ const newSegment: AudioSegment = {
+ id: Date.now().toString(),
+ startTime: newSegmentStart,
+ endTime: currentTime,
+ transcription: "",
+ label: audioLabels[0].name,
+ speaker: "",
+ };
+
+ setSegments([...segments, newSegment]);
+ setIsCreatingSegment(false);
+ setEditingSegment(newSegment);
+
+ toast({
+ title: "片段已创建",
+ description: `时长: ${formatTime(currentTime - newSegmentStart)}`,
+ });
+ };
+
+ const deleteSegment = (id: string) => {
+ setSegments(segments.filter((s) => s.id !== id));
+ setSelectedSegment(null);
+ toast({
+ title: "片段已删除",
+ description: "音频片段已被删除",
+ });
+ };
+
+ const updateSegment = (updatedSegment: AudioSegment) => {
+ setSegments(
+ segments.map((s) => (s.id === updatedSegment.id ? updatedSegment : s))
+ );
+ setEditingSegment(null);
+ toast({
+ title: "片段已更新",
+ description: "转录内容已保存",
+ });
+ };
+
+ const playSegment = (segment: AudioSegment) => {
+ handleSeek(segment.startTime);
+ setSelectedSegment(segment.id);
+
+ const audio = audioRef.current;
+ if (!audio) return;
+
+ audio.play();
+ setIsPlaying(true);
+
+ // 在片段结束时暂停
+ const checkEnd = () => {
+ if (audio.currentTime >= segment.endTime) {
+ audio.pause();
+ setIsPlaying(false);
+ audio.removeEventListener("timeupdate", checkEnd);
+ }
+ };
+ audio.addEventListener("timeupdate", checkEnd);
+ };
+
+ const formatTime = (seconds: number) => {
+ const mins = Math.floor(seconds / 60);
+ const secs = Math.floor(seconds % 60);
+ return `${mins.toString().padStart(2, "0")}:${secs
+ .toString()
+ .padStart(2, "0")}`;
+ };
+
+ const getSegmentColor = (label: string) => {
+ const labelConfig = audioLabels.find((l) => l.name === label);
+ return labelConfig?.color || "#6B7280";
+ };
+
+ return (
+
+ {/* Audio Player */}
+
+
+ {/* Audio Element */}
+
+
+ {/* Player Controls */}
+
+
handleSeek(Math.max(0, currentTime - 10))}
+ >
+
+
+
+ {isPlaying ? (
+
+ ) : (
+
+ )}
+
+
handleSeek(Math.min(duration, currentTime + 10))}
+ >
+
+
+
+
+ {/* Timeline */}
+
+
+ {formatTime(currentTime)}
+ {formatTime(duration)}
+
+
+
handleSeek(value[0])}
+ className="w-full"
+ />
+ {/* Segment Visualization */}
+
+ {segments.map((segment) => {
+ const left = (segment.startTime / duration) * 100;
+ const width =
+ ((segment.endTime - segment.startTime) / duration) * 100;
+ return (
+
+ );
+ })}
+
+ {/* Current Creating Segment */}
+ {isCreatingSegment && (
+
+ )}
+
+
+
+ {/* Volume Control */}
+
+
+ {isMuted ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {/* Annotation Controls */}
+
+ {isCreatingSegment ? (
+
+
+ 完成片段
+
+ ) : (
+
+
+ 创建片段
+
+ )}
+
+
+
+
+ {/* Main Content */}
+
+ {/* Segments List */}
+
+
+
+
音频片段
+ {segments.length} 个片段
+
+
+
+
+ {segments.map((segment) => (
+
setSelectedSegment(segment.id)}
+ >
+
+
+
+
+
+
+ {segment.label}
+
+
+
+
{
+ e.stopPropagation();
+ playSegment(segment);
+ }}
+ >
+
+
+
{
+ e.stopPropagation();
+ setEditingSegment(segment);
+ }}
+ >
+
+
+
{
+ e.stopPropagation();
+ deleteSegment(segment.id);
+ }}
+ >
+
+
+
+
+
+ {formatTime(segment.startTime)} -{" "}
+ {formatTime(segment.endTime)}
+ {segment.speaker && ` | ${segment.speaker}`}
+
+
+ {segment.transcription || "未转录"}
+
+ {segment.confidence && (
+
+
+ 置信度:
+
+
+ {(segment.confidence * 100).toFixed(1)}%
+
+
+ )}
+
+
+
+ ))}
+
+
+
+
+
+ {/* Transcription Editor */}
+
+ {editingSegment ? (
+
+
+
+
+ 编辑转录
+
+
+
+
+
+
+
+
+
+
+
+
+
+ setEditingSegment({
+ ...editingSegment,
+ speaker: e.target.value,
+ })
+ }
+ placeholder="输入说话人名称"
+ className="mt-1"
+ />
+
+
+
+
+
+
+
+ setEditingSegment(null)}
+ >
+ 取消
+
+ updateSegment(editingSegment)}
+ className="bg-blue-600 hover:bg-blue-700"
+ >
+
+ 保存
+
+
+
+
+ ) : selectedSegment ? (
+
+
+
+
+ 片段详情
+
+
+
+ {(() => {
+ const segment = segments.find(
+ (s) => s.id === selectedSegment
+ );
+ if (!segment) return null;
+
+ return (
+
+
+
+
+ 时间范围
+
+
+ {formatTime(segment.startTime)} -{" "}
+ {formatTime(segment.endTime)}
+
+
+
+
时长
+
+ {formatTime(segment.endTime - segment.startTime)}
+
+
+
+
+
+
+ {segment.speaker && (
+
+
说话人
+
{segment.speaker}
+
+ )}
+
+
+
转录内容
+
+ {segment.transcription || "暂无转录内容"}
+
+
+
+ {segment.confidence && (
+
+
置信度
+
+ {(segment.confidence * 100).toFixed(1)}%
+
+
+ )}
+
+
+
playSegment(segment)}
+ variant="outline"
+ >
+
+ 播放片段
+
+
setEditingSegment(segment)}
+ variant="outline"
+ >
+
+ 编辑
+
+
+
+ );
+ })()}
+
+
+ ) : (
+
+
+
+
+ 音频标注工作区
+
+
+ 选择一个音频片段开始编辑转录内容
+
+
+
• 点击"创建片段"开始标记音频片段
+
• 选择片段进行转录和标注
+
• 使用播放控件精确定位音频位置
+
+
+
+ )}
+
+
+
+ {/* Bottom Actions */}
+
+
+
+ 文件: {currentAudio.name} | 片段: {segments.length} | 总时长:{" "}
+ {formatTime(duration)}
+
+
+
+ 跳过
+
+
+
+ 保存并下一个
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/DataAnnotation/Annotate/components/ImageAnnotation.tsx b/frontend/src/pages/DataAnnotation/Annotate/components/ImageAnnotation.tsx
index 911550e27..8d95e2bdf 100644
--- a/frontend/src/pages/DataAnnotation/Annotate/components/ImageAnnotation.tsx
+++ b/frontend/src/pages/DataAnnotation/Annotate/components/ImageAnnotation.tsx
@@ -1,617 +1,617 @@
-import type React from "react";
-import { useState, useRef, useEffect } from "react";
-import { Button, Badge, Checkbox, message } from "antd";
-import {
- Square,
- Circle,
- MousePointer,
- ZoomIn,
- ZoomOut,
- RotateCcw,
- ArrowLeft,
- ArrowRight,
- MoreHorizontal,
-} from "lucide-react";
-
-interface Annotation {
- id: string;
- type: "rectangle" | "circle" | "polygon";
- label: string;
- color: string;
- coordinates: number[];
- visible: boolean;
-}
-
-interface ImageAnnotationWorkspaceProps {
- task: any;
- currentFileIndex: number;
- onSaveAndNext: () => void;
- onSkipAndNext: () => void;
-}
-
-// 模拟医学图像数据
-const mockMedicalImages = [
- {
- id: "1",
- name: "2024-123456",
- thumbnail: "/placeholder.svg?height=60&width=60&text=Slide1",
- url: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/img_v3_02oi_e6dd5540-9ca4-4277-ad2b-4debaa1c8ddg.jpg-oibLbUmFpZMkLTmwZB7lT1UWKFlOLA.jpeg",
- },
- {
- id: "2",
- name: "2024-234567",
- thumbnail: "/placeholder.svg?height=60&width=60&text=Slide2",
- url: "/placeholder.svg?height=600&width=800&text=Medical Image 2",
- },
- {
- id: "3",
- name: "2025-345678",
- thumbnail: "/placeholder.svg?height=60&width=60&text=Slide3",
- url: "/placeholder.svg?height=600&width=800&text=Medical Image 3",
- },
- {
- id: "4",
- name: "1234-123456",
- thumbnail: "/placeholder.svg?height=60&width=60&text=Slide4",
- url: "/placeholder.svg?height=600&width=800&text=Medical Image 4",
- },
- {
- id: "5",
- name: "2025-456789",
- thumbnail: "/placeholder.svg?height=60&width=60&text=Slide5",
- url: "/placeholder.svg?height=600&width=800&text=Medical Image 5",
- },
- {
- id: "6",
- name: "2025-567890",
- thumbnail: "/placeholder.svg?height=60&width=60&text=Slide6",
- url: "/placeholder.svg?height=600&width=800&text=Medical Image 6",
- },
- {
- id: "7",
- name: "2025-678901",
- thumbnail: "/placeholder.svg?height=60&width=60&text=Slide7",
- url: "/placeholder.svg?height=600&width=800&text=Medical Image 7",
- },
- {
- id: "8",
- name: "2025-789012",
- thumbnail: "/placeholder.svg?height=60&width=60&text=Slide8",
- url: "/placeholder.svg?height=600&width=800&text=Medical Image 8",
- },
- {
- id: "9",
- name: "2025-890123",
- thumbnail: "/placeholder.svg?height=60&width=60&text=Slide9",
- url: "/placeholder.svg?height=600&width=800&text=Medical Image 9",
- },
- {
- id: "10",
- name: "2025-901234",
- thumbnail: "/placeholder.svg?height=60&width=60&text=Slide10",
- url: "/placeholder.svg?height=600&width=800&text=Medical Image 10",
- },
-];
-
-// 医学标注选项
-const medicalAnnotationOptions = [
- {
- id: "tumor_present",
- label: "是否有肿瘤",
- type: "radio",
- options: ["是", "否"],
- },
- {
- id: "tumor_type",
- label: "肿瘤形成",
- type: "checkbox",
- options: ["腺管形成"],
- },
- { id: "grade_1", label: "1级", type: "checkbox", options: ["1[x]"] },
- { id: "grade_2", label: "2级", type: "checkbox", options: ["2[x]"] },
- { id: "remarks", label: "备注", type: "textarea" },
- {
- id: "nuclear_polymorphism",
- label: "核多形性",
- type: "checkbox",
- options: ["核分裂象"],
- },
- {
- id: "histological_type",
- label: "组织学类型",
- type: "checkbox",
- options: ["1[b]", "2[y]", "3[t]"],
- },
- {
- id: "small_time_lesion",
- label: "小时病位置[3]",
- type: "checkbox",
- options: ["1[b]", "2[y]", "3[t]"],
- },
- {
- id: "ductal_position",
- label: "导管原位置[4]",
- type: "checkbox",
- options: ["1[o]", "2[p]", "3[t]"],
- },
- {
- id: "ductal_position_large",
- label: "导管原位置件大于腺分",
- type: "checkbox",
- options: ["腺分裂象"],
- },
- {
- id: "mitosis",
- label: "化[5]",
- type: "checkbox",
- options: ["1[o]", "2[p]", "3[t]"],
- },
- {
- id: "original_position",
- label: "原位实性乳头状[6]",
- type: "checkbox",
- options: ["1[o]", "2[p]", "3[t]"],
- },
- {
- id: "infiltrating_lesion",
- label: "浸润性病(非特殊型)[7]",
- type: "checkbox",
- options: ["1[o]", "2[p]", "3[t]"],
- },
- {
- id: "infiltrating_small",
- label: "浸润性小叶癌[8]",
- type: "checkbox",
- options: ["脉管侵犯"],
- },
- {
- id: "infiltrating_real",
- label: "浸润实性乳头状癌[9]",
- type: "checkbox",
- options: ["1[o]", "2[p]", "3[t]"],
- },
- {
- id: "other_lesion",
- label: "其他病[0]",
- type: "checkbox",
- options: ["+[k]"],
- },
-];
-
-export default function ImageAnnotationWorkspace({
- currentFileIndex,
-}: ImageAnnotationWorkspaceProps) {
- const canvasRef = useRef(null);
- const [selectedImageIndex, setSelectedImageIndex] = useState(
- currentFileIndex || 0
- );
- const [currentImage, setCurrentImage] = useState(
- mockMedicalImages[selectedImageIndex]
- );
- const [annotations, setAnnotations] = useState([]);
- const [selectedTool, setSelectedTool] = useState<
- "select" | "rectangle" | "circle"
- >("select");
- const [isDrawing, setIsDrawing] = useState(false);
- const [startPoint, setStartPoint] = useState({ x: 0, y: 0 });
- const [zoom, setZoom] = useState(1);
- const [pan, setPan] = useState({ x: 0, y: 0 });
- const [selectedAnnotation, setSelectedAnnotation] = useState(
- null
- );
- const [annotationValues, setAnnotationValues] = useState>(
- {}
- );
-
- useEffect(() => {
- setCurrentImage(mockMedicalImages[selectedImageIndex]);
- drawCanvas();
- }, [selectedImageIndex, annotations, zoom, pan]);
-
- const drawCanvas = () => {
- const canvas = canvasRef.current;
- if (!canvas) return;
-
- const ctx = canvas.getContext("2d");
- if (!ctx) return;
-
- ctx.clearRect(0, 0, canvas.width, canvas.height);
-
- const img = new Image();
- img.crossOrigin = "anonymous";
- img.onload = () => {
- ctx.save();
- ctx.scale(zoom, zoom);
- ctx.translate(pan.x, pan.y);
- ctx.drawImage(img, 0, 0, canvas.width / zoom, canvas.height / zoom);
-
- // 绘制标注
- annotations.forEach((annotation) => {
- if (!annotation.visible) return;
-
- ctx.strokeStyle = annotation.color;
- ctx.fillStyle = annotation.color + "20";
- ctx.lineWidth = 2;
-
- if (annotation.type === "rectangle") {
- const [x, y, width, height] = annotation.coordinates;
- ctx.strokeRect(x, y, width, height);
- ctx.fillRect(x, y, width, height);
- } else if (annotation.type === "circle") {
- const [centerX, centerY, radius] = annotation.coordinates;
- ctx.beginPath();
- ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
- ctx.stroke();
- ctx.fill();
- }
-
- if (selectedAnnotation === annotation.id) {
- ctx.strokeStyle = "#FF0000";
- ctx.lineWidth = 3;
- ctx.setLineDash([5, 5]);
-
- if (annotation.type === "rectangle") {
- const [x, y, width, height] = annotation.coordinates;
- ctx.strokeRect(x - 2, y - 2, width + 4, height + 4);
- } else if (annotation.type === "circle") {
- const [centerX, centerY, radius] = annotation.coordinates;
- ctx.beginPath();
- ctx.arc(centerX, centerY, radius + 2, 0, 2 * Math.PI);
- ctx.stroke();
- }
-
- ctx.setLineDash([]);
- }
- });
-
- ctx.restore();
- };
- img.src = currentImage.url;
- };
-
- const handleCanvasMouseDown = (e: React.MouseEvent) => {
- const canvas = canvasRef.current;
- if (!canvas) return;
-
- const rect = canvas.getBoundingClientRect();
- const x = (e.clientX - rect.left - pan.x) / zoom;
- const y = (e.clientY - rect.top - pan.y) / zoom;
-
- if (selectedTool === "rectangle" || selectedTool === "circle") {
- setIsDrawing(true);
- setStartPoint({ x, y });
- } else if (selectedTool === "select") {
- const clickedAnnotation = annotations.find((annotation) => {
- if (annotation.type === "rectangle") {
- const [ax, ay, width, height] = annotation.coordinates;
- return x >= ax && x <= ax + width && y >= ay && y <= ay + height;
- } else if (annotation.type === "circle") {
- const [centerX, centerY, radius] = annotation.coordinates;
- const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
- return distance <= radius;
- }
- return false;
- });
-
- setSelectedAnnotation(clickedAnnotation?.id || null);
- }
- };
-
- const handleCanvasMouseMove = (e: React.MouseEvent) => {
- if (!isDrawing) return;
-
- const canvas = canvasRef.current;
- if (!canvas) return;
-
- const rect = canvas.getBoundingClientRect();
- const x = (e.clientX - rect.left - pan.x) / zoom;
- const y = (e.clientY - rect.top - pan.y) / zoom;
-
- drawCanvas();
- const ctx = canvas.getContext("2d");
- if (!ctx) return;
-
- ctx.save();
- ctx.scale(zoom, zoom);
- ctx.translate(pan.x, pan.y);
- ctx.strokeStyle = "#3B82F6";
- ctx.lineWidth = 2;
- ctx.setLineDash([5, 5]);
-
- if (selectedTool === "rectangle") {
- const width = x - startPoint.x;
- const height = y - startPoint.y;
- ctx.strokeRect(startPoint.x, startPoint.y, width, height);
- } else if (selectedTool === "circle") {
- const radius = Math.sqrt(
- (x - startPoint.x) ** 2 + (y - startPoint.y) ** 2
- );
- ctx.beginPath();
- ctx.arc(startPoint.x, startPoint.y, radius, 0, 2 * Math.PI);
- ctx.stroke();
- }
-
- ctx.restore();
- };
-
- const handleCanvasMouseUp = (e: React.MouseEvent) => {
- if (!isDrawing) return;
-
- const canvas = canvasRef.current;
- if (!canvas) return;
-
- const rect = canvas.getBoundingClientRect();
- const x = (e.clientX - rect.left - pan.x) / zoom;
- const y = (e.clientY - rect.top - pan.y) / zoom;
-
- let coordinates: number[] = [];
-
- if (selectedTool === "rectangle") {
- const width = x - startPoint.x;
- const height = y - startPoint.y;
- coordinates = [startPoint.x, startPoint.y, width, height];
- } else if (selectedTool === "circle") {
- const radius = Math.sqrt(
- (x - startPoint.x) ** 2 + (y - startPoint.y) ** 2
- );
- coordinates = [startPoint.x, startPoint.y, radius];
- }
-
- if (coordinates.length > 0) {
- const newAnnotation: Annotation = {
- id: Date.now().toString(),
- type: selectedTool as "rectangle" | "circle",
- label: "标注",
- color: "#3B82F6",
- coordinates,
- visible: true,
- };
-
- setAnnotations([...annotations, newAnnotation]);
- }
-
- setIsDrawing(false);
- };
-
- const handleAnnotationValueChange = (optionId: string, value: any) => {
- setAnnotationValues((prev) => ({
- ...prev,
- [optionId]: value,
- }));
- };
-
- const handleUpdate = () => {
- message({
- title: "标注已更新",
- description: "医学标注信息已保存",
- });
- };
-
- return (
-
- {/* Left Sidebar - Image List */}
-
- {/* Header */}
-
-
-
- image
- img
-
-
- case_id
- #13754
-
-
-
-
-
-
-
-
- {/* Image List */}
-
- {mockMedicalImages.map((image, index) => (
-
setSelectedImageIndex(index)}
- >
-
- {index + 1}
-
-

-
-
- ))}
-
-
-
- {/* Main Content Area */}
-
- {/* Main Image Display */}
-
-
-
-
WSI图像预览
-
-
- 病理号: 1234-123456
-
-
- 取材部位: 余乳
-
-
-
-
-
-
-
- {/* Zoom Controls */}
-
- setZoom(Math.max(zoom / 1.2, 0.1))}>
-
-
- {Math.round(zoom * 100)}%
- setZoom(Math.min(zoom * 1.2, 5))}>
-
-
- {
- setZoom(1);
- setPan({ x: 0, y: 0 });
- }}
- >
-
-
-
-
- {/* Tool Selection */}
-
- setSelectedTool("select")}
- >
-
-
- setSelectedTool("rectangle")}
- >
-
-
- setSelectedTool("circle")}
- >
-
-
-
-
-
- {/* Navigation Controls */}
-
-
-
-
-
- {/* Right Sidebar - Annotation Panel */}
-
-
-
-
标注
-
-
- {medicalAnnotationOptions.map((option) => (
-
-
{option.label}
-
- {option.type === "radio" && (
-
- {option.options?.map((opt) => (
-
-
- handleAnnotationValueChange(
- option.id,
- e.target.value
- )
- }
- className="w-4 h-4"
- />
- {opt}
-
- ))}
-
- )}
-
- {option.type === "checkbox" && (
-
- {option.options?.map((opt) => (
-
-
- handleAnnotationValueChange(
- `${option.id}_${opt}`,
- checked
- )
- }
- />
- {opt}
-
- ))}
-
- )}
-
- {option.type === "textarea" && (
-
- ))}
-
-
-
-
-
- Update
-
-
-
-
-
- );
-}
+import type React from "react";
+import { useState, useRef, useEffect } from "react";
+import { Button, Badge, Checkbox, message } from "antd";
+import {
+ Square,
+ Circle,
+ MousePointer,
+ ZoomIn,
+ ZoomOut,
+ RotateCcw,
+ ArrowLeft,
+ ArrowRight,
+ MoreHorizontal,
+} from "lucide-react";
+
+interface Annotation {
+ id: string;
+ type: "rectangle" | "circle" | "polygon";
+ label: string;
+ color: string;
+ coordinates: number[];
+ visible: boolean;
+}
+
+interface ImageAnnotationWorkspaceProps {
+ task: any;
+ currentFileIndex: number;
+ onSaveAndNext: () => void;
+ onSkipAndNext: () => void;
+}
+
+// 模拟医学图像数据
+const mockMedicalImages = [
+ {
+ id: "1",
+ name: "2024-123456",
+ thumbnail: "/placeholder.svg?height=60&width=60&text=Slide1",
+ url: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/img_v3_02oi_e6dd5540-9ca4-4277-ad2b-4debaa1c8ddg.jpg-oibLbUmFpZMkLTmwZB7lT1UWKFlOLA.jpeg",
+ },
+ {
+ id: "2",
+ name: "2024-234567",
+ thumbnail: "/placeholder.svg?height=60&width=60&text=Slide2",
+ url: "/placeholder.svg?height=600&width=800&text=Medical Image 2",
+ },
+ {
+ id: "3",
+ name: "2025-345678",
+ thumbnail: "/placeholder.svg?height=60&width=60&text=Slide3",
+ url: "/placeholder.svg?height=600&width=800&text=Medical Image 3",
+ },
+ {
+ id: "4",
+ name: "1234-123456",
+ thumbnail: "/placeholder.svg?height=60&width=60&text=Slide4",
+ url: "/placeholder.svg?height=600&width=800&text=Medical Image 4",
+ },
+ {
+ id: "5",
+ name: "2025-456789",
+ thumbnail: "/placeholder.svg?height=60&width=60&text=Slide5",
+ url: "/placeholder.svg?height=600&width=800&text=Medical Image 5",
+ },
+ {
+ id: "6",
+ name: "2025-567890",
+ thumbnail: "/placeholder.svg?height=60&width=60&text=Slide6",
+ url: "/placeholder.svg?height=600&width=800&text=Medical Image 6",
+ },
+ {
+ id: "7",
+ name: "2025-678901",
+ thumbnail: "/placeholder.svg?height=60&width=60&text=Slide7",
+ url: "/placeholder.svg?height=600&width=800&text=Medical Image 7",
+ },
+ {
+ id: "8",
+ name: "2025-789012",
+ thumbnail: "/placeholder.svg?height=60&width=60&text=Slide8",
+ url: "/placeholder.svg?height=600&width=800&text=Medical Image 8",
+ },
+ {
+ id: "9",
+ name: "2025-890123",
+ thumbnail: "/placeholder.svg?height=60&width=60&text=Slide9",
+ url: "/placeholder.svg?height=600&width=800&text=Medical Image 9",
+ },
+ {
+ id: "10",
+ name: "2025-901234",
+ thumbnail: "/placeholder.svg?height=60&width=60&text=Slide10",
+ url: "/placeholder.svg?height=600&width=800&text=Medical Image 10",
+ },
+];
+
+// 医学标注选项
+const medicalAnnotationOptions = [
+ {
+ id: "tumor_present",
+ label: "是否有肿瘤",
+ type: "radio",
+ options: ["是", "否"],
+ },
+ {
+ id: "tumor_type",
+ label: "肿瘤形成",
+ type: "checkbox",
+ options: ["腺管形成"],
+ },
+ { id: "grade_1", label: "1级", type: "checkbox", options: ["1[x]"] },
+ { id: "grade_2", label: "2级", type: "checkbox", options: ["2[x]"] },
+ { id: "remarks", label: "备注", type: "textarea" },
+ {
+ id: "nuclear_polymorphism",
+ label: "核多形性",
+ type: "checkbox",
+ options: ["核分裂象"],
+ },
+ {
+ id: "histological_type",
+ label: "组织学类型",
+ type: "checkbox",
+ options: ["1[b]", "2[y]", "3[t]"],
+ },
+ {
+ id: "small_time_lesion",
+ label: "小时病位置[3]",
+ type: "checkbox",
+ options: ["1[b]", "2[y]", "3[t]"],
+ },
+ {
+ id: "ductal_position",
+ label: "导管原位置[4]",
+ type: "checkbox",
+ options: ["1[o]", "2[p]", "3[t]"],
+ },
+ {
+ id: "ductal_position_large",
+ label: "导管原位置件大于腺分",
+ type: "checkbox",
+ options: ["腺分裂象"],
+ },
+ {
+ id: "mitosis",
+ label: "化[5]",
+ type: "checkbox",
+ options: ["1[o]", "2[p]", "3[t]"],
+ },
+ {
+ id: "original_position",
+ label: "原位实性乳头状[6]",
+ type: "checkbox",
+ options: ["1[o]", "2[p]", "3[t]"],
+ },
+ {
+ id: "infiltrating_lesion",
+ label: "浸润性病(非特殊型)[7]",
+ type: "checkbox",
+ options: ["1[o]", "2[p]", "3[t]"],
+ },
+ {
+ id: "infiltrating_small",
+ label: "浸润性小叶癌[8]",
+ type: "checkbox",
+ options: ["脉管侵犯"],
+ },
+ {
+ id: "infiltrating_real",
+ label: "浸润实性乳头状癌[9]",
+ type: "checkbox",
+ options: ["1[o]", "2[p]", "3[t]"],
+ },
+ {
+ id: "other_lesion",
+ label: "其他病[0]",
+ type: "checkbox",
+ options: ["+[k]"],
+ },
+];
+
+export default function ImageAnnotationWorkspace({
+ currentFileIndex,
+}: ImageAnnotationWorkspaceProps) {
+ const canvasRef = useRef(null);
+ const [selectedImageIndex, setSelectedImageIndex] = useState(
+ currentFileIndex || 0
+ );
+ const [currentImage, setCurrentImage] = useState(
+ mockMedicalImages[selectedImageIndex]
+ );
+ const [annotations, setAnnotations] = useState([]);
+ const [selectedTool, setSelectedTool] = useState<
+ "select" | "rectangle" | "circle"
+ >("select");
+ const [isDrawing, setIsDrawing] = useState(false);
+ const [startPoint, setStartPoint] = useState({ x: 0, y: 0 });
+ const [zoom, setZoom] = useState(1);
+ const [pan, setPan] = useState({ x: 0, y: 0 });
+ const [selectedAnnotation, setSelectedAnnotation] = useState(
+ null
+ );
+ const [annotationValues, setAnnotationValues] = useState>(
+ {}
+ );
+
+ useEffect(() => {
+ setCurrentImage(mockMedicalImages[selectedImageIndex]);
+ drawCanvas();
+ }, [selectedImageIndex, annotations, zoom, pan]);
+
+ const drawCanvas = () => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ const img = new Image();
+ img.crossOrigin = "anonymous";
+ img.onload = () => {
+ ctx.save();
+ ctx.scale(zoom, zoom);
+ ctx.translate(pan.x, pan.y);
+ ctx.drawImage(img, 0, 0, canvas.width / zoom, canvas.height / zoom);
+
+ // 绘制标注
+ annotations.forEach((annotation) => {
+ if (!annotation.visible) return;
+
+ ctx.strokeStyle = annotation.color;
+ ctx.fillStyle = annotation.color + "20";
+ ctx.lineWidth = 2;
+
+ if (annotation.type === "rectangle") {
+ const [x, y, width, height] = annotation.coordinates;
+ ctx.strokeRect(x, y, width, height);
+ ctx.fillRect(x, y, width, height);
+ } else if (annotation.type === "circle") {
+ const [centerX, centerY, radius] = annotation.coordinates;
+ ctx.beginPath();
+ ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
+ ctx.stroke();
+ ctx.fill();
+ }
+
+ if (selectedAnnotation === annotation.id) {
+ ctx.strokeStyle = "#FF0000";
+ ctx.lineWidth = 3;
+ ctx.setLineDash([5, 5]);
+
+ if (annotation.type === "rectangle") {
+ const [x, y, width, height] = annotation.coordinates;
+ ctx.strokeRect(x - 2, y - 2, width + 4, height + 4);
+ } else if (annotation.type === "circle") {
+ const [centerX, centerY, radius] = annotation.coordinates;
+ ctx.beginPath();
+ ctx.arc(centerX, centerY, radius + 2, 0, 2 * Math.PI);
+ ctx.stroke();
+ }
+
+ ctx.setLineDash([]);
+ }
+ });
+
+ ctx.restore();
+ };
+ img.src = currentImage.url;
+ };
+
+ const handleCanvasMouseDown = (e: React.MouseEvent) => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+
+ const rect = canvas.getBoundingClientRect();
+ const x = (e.clientX - rect.left - pan.x) / zoom;
+ const y = (e.clientY - rect.top - pan.y) / zoom;
+
+ if (selectedTool === "rectangle" || selectedTool === "circle") {
+ setIsDrawing(true);
+ setStartPoint({ x, y });
+ } else if (selectedTool === "select") {
+ const clickedAnnotation = annotations.find((annotation) => {
+ if (annotation.type === "rectangle") {
+ const [ax, ay, width, height] = annotation.coordinates;
+ return x >= ax && x <= ax + width && y >= ay && y <= ay + height;
+ } else if (annotation.type === "circle") {
+ const [centerX, centerY, radius] = annotation.coordinates;
+ const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
+ return distance <= radius;
+ }
+ return false;
+ });
+
+ setSelectedAnnotation(clickedAnnotation?.id || null);
+ }
+ };
+
+ const handleCanvasMouseMove = (e: React.MouseEvent) => {
+ if (!isDrawing) return;
+
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+
+ const rect = canvas.getBoundingClientRect();
+ const x = (e.clientX - rect.left - pan.x) / zoom;
+ const y = (e.clientY - rect.top - pan.y) / zoom;
+
+ drawCanvas();
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return;
+
+ ctx.save();
+ ctx.scale(zoom, zoom);
+ ctx.translate(pan.x, pan.y);
+ ctx.strokeStyle = "#3B82F6";
+ ctx.lineWidth = 2;
+ ctx.setLineDash([5, 5]);
+
+ if (selectedTool === "rectangle") {
+ const width = x - startPoint.x;
+ const height = y - startPoint.y;
+ ctx.strokeRect(startPoint.x, startPoint.y, width, height);
+ } else if (selectedTool === "circle") {
+ const radius = Math.sqrt(
+ (x - startPoint.x) ** 2 + (y - startPoint.y) ** 2
+ );
+ ctx.beginPath();
+ ctx.arc(startPoint.x, startPoint.y, radius, 0, 2 * Math.PI);
+ ctx.stroke();
+ }
+
+ ctx.restore();
+ };
+
+ const handleCanvasMouseUp = (e: React.MouseEvent) => {
+ if (!isDrawing) return;
+
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+
+ const rect = canvas.getBoundingClientRect();
+ const x = (e.clientX - rect.left - pan.x) / zoom;
+ const y = (e.clientY - rect.top - pan.y) / zoom;
+
+ let coordinates: number[] = [];
+
+ if (selectedTool === "rectangle") {
+ const width = x - startPoint.x;
+ const height = y - startPoint.y;
+ coordinates = [startPoint.x, startPoint.y, width, height];
+ } else if (selectedTool === "circle") {
+ const radius = Math.sqrt(
+ (x - startPoint.x) ** 2 + (y - startPoint.y) ** 2
+ );
+ coordinates = [startPoint.x, startPoint.y, radius];
+ }
+
+ if (coordinates.length > 0) {
+ const newAnnotation: Annotation = {
+ id: Date.now().toString(),
+ type: selectedTool as "rectangle" | "circle",
+ label: "标注",
+ color: "#3B82F6",
+ coordinates,
+ visible: true,
+ };
+
+ setAnnotations([...annotations, newAnnotation]);
+ }
+
+ setIsDrawing(false);
+ };
+
+ const handleAnnotationValueChange = (optionId: string, value: any) => {
+ setAnnotationValues((prev) => ({
+ ...prev,
+ [optionId]: value,
+ }));
+ };
+
+ const handleUpdate = () => {
+ message({
+ title: "标注已更新",
+ description: "医学标注信息已保存",
+ });
+ };
+
+ return (
+
+ {/* Left Sidebar - Image List */}
+
+ {/* Header */}
+
+
+
+ image
+ img
+
+
+ case_id
+ #13754
+
+
+
+
+
+
+
+
+ {/* Image List */}
+
+ {mockMedicalImages.map((image, index) => (
+
setSelectedImageIndex(index)}
+ >
+
+ {index + 1}
+
+

+
+
+ ))}
+
+
+
+ {/* Main Content Area */}
+
+ {/* Main Image Display */}
+
+
+
+
WSI图像预览
+
+
+ 病理号: 1234-123456
+
+
+ 取材部位: 余乳
+
+
+
+
+
+
+
+ {/* Zoom Controls */}
+
+ setZoom(Math.max(zoom / 1.2, 0.1))}>
+
+
+ {Math.round(zoom * 100)}%
+ setZoom(Math.min(zoom * 1.2, 5))}>
+
+
+ {
+ setZoom(1);
+ setPan({ x: 0, y: 0 });
+ }}
+ >
+
+
+
+
+ {/* Tool Selection */}
+
+ setSelectedTool("select")}
+ >
+
+
+ setSelectedTool("rectangle")}
+ >
+
+
+ setSelectedTool("circle")}
+ >
+
+
+
+
+
+ {/* Navigation Controls */}
+
+
+
+
+
+ {/* Right Sidebar - Annotation Panel */}
+
+
+
+
标注
+
+
+ {medicalAnnotationOptions.map((option) => (
+
+
{option.label}
+
+ {option.type === "radio" && (
+
+ {option.options?.map((opt) => (
+
+
+ handleAnnotationValueChange(
+ option.id,
+ e.target.value
+ )
+ }
+ className="w-4 h-4"
+ />
+ {opt}
+
+ ))}
+
+ )}
+
+ {option.type === "checkbox" && (
+
+ {option.options?.map((opt) => (
+
+
+ handleAnnotationValueChange(
+ `${option.id}_${opt}`,
+ checked
+ )
+ }
+ />
+ {opt}
+
+ ))}
+
+ )}
+
+ {option.type === "textarea" && (
+
+ ))}
+
+
+
+
+
+ Update
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/DataAnnotation/Annotate/components/TextAnnotation.tsx b/frontend/src/pages/DataAnnotation/Annotate/components/TextAnnotation.tsx
index c5816ddbb..e0ab71dc7 100644
--- a/frontend/src/pages/DataAnnotation/Annotate/components/TextAnnotation.tsx
+++ b/frontend/src/pages/DataAnnotation/Annotate/components/TextAnnotation.tsx
@@ -1,457 +1,457 @@
-import { useState } from "react";
-import { Card, Button, Badge, Input, Checkbox } from "antd";
-
-import {
- File,
- Search,
- CheckCircle,
- ThumbsUp,
- ThumbsDown,
- MessageSquare,
- HelpCircle,
-} from "lucide-react";
-
-interface QAPair {
- id: string;
- question: string;
- answer: string;
- status: "pending" | "approved" | "rejected";
- confidence?: number;
-}
-
-interface FileData {
- id: string;
- name: string;
- qaPairs: QAPair[];
-}
-
-interface TextAnnotationWorkspaceProps {
- task: any;
- currentFileIndex: number;
- onSaveAndNext: () => void;
- onSkipAndNext: () => void;
-}
-
-// 模拟文件数据
-const mockFiles: FileData[] = [
- {
- id: "1",
- name: "document_001.txt",
- qaPairs: [
- {
- id: "1",
- question: "什么是人工智能?",
- answer:
- "人工智能(AI)是计算机科学的一个分支,致力于创建能够执行通常需要人类智能的任务的系统。",
- status: "pending",
- confidence: 0.85,
- },
- {
- id: "2",
- question: "机器学习和深度学习有什么区别?",
- answer:
- "机器学习是人工智能的一个子集,而深度学习是机器学习的一个子集。深度学习使用神经网络来模拟人脑的工作方式。",
- status: "pending",
- confidence: 0.92,
- },
- {
- id: "3",
- question: "什么是神经网络?",
- answer:
- "神经网络是一种受生物神经网络启发的计算模型,由相互连接的节点(神经元)组成,能够学习和识别模式。",
- status: "pending",
- confidence: 0.78,
- },
- ],
- },
- {
- id: "2",
- name: "document_002.txt",
- qaPairs: [
- {
- id: "4",
- question: "什么是自然语言处理?",
- answer:
- "自然语言处理(NLP)是人工智能的一个分支,专注于使计算机能够理解、解释和生成人类语言。",
- status: "pending",
- confidence: 0.88,
- },
- {
- id: "5",
- question: "计算机视觉的应用有哪些?",
- answer:
- "计算机视觉广泛应用于图像识别、人脸识别、自动驾驶、医学影像分析、安防监控等领域。",
- status: "pending",
- confidence: 0.91,
- },
- ],
- },
-];
-
-export default function TextAnnotationWorkspace({
- onSaveAndNext,
- onSkipAndNext,
-}: TextAnnotationWorkspaceProps) {
- const [selectedFile, setSelectedFile] = useState(
- mockFiles[0]
- );
- const [searchQuery, setSearchQuery] = useState("");
- const [statusFilter, setStatusFilter] = useState("all");
- const [selectedQAs, setSelectedQAs] = useState([]);
-
- const handleFileSelect = (file: FileData) => {
- setSelectedFile(file);
- setSelectedQAs([]);
- };
-
- const handleQAStatusChange = (
- qaId: string,
- status: "approved" | "rejected"
- ) => {
- if (selectedFile) {
- const updatedFile = {
- ...selectedFile,
- qaPairs: selectedFile.qaPairs.map((qa) =>
- qa.id === qaId ? { ...qa, status } : qa
- ),
- };
- setSelectedFile(updatedFile);
-
- message({
- title: status === "approved" ? "已标记为留用" : "已标记为不留用",
- description: `QA对 "${qaId}" 状态已更新`,
- });
- }
- };
-
- const handleBatchApprove = () => {
- if (selectedFile && selectedQAs.length > 0) {
- const updatedFile = {
- ...selectedFile,
- qaPairs: selectedFile.qaPairs.map((qa) =>
- selectedQAs.includes(qa.id)
- ? { ...qa, status: "approved" as const }
- : qa
- ),
- };
- setSelectedFile(updatedFile);
- setSelectedQAs([]);
-
- message({
- title: "批量操作完成",
- description: `已将 ${selectedQAs.length} 个QA对标记为留用`,
- });
- }
- };
-
- const handleBatchReject = () => {
- if (selectedFile && selectedQAs.length > 0) {
- const updatedFile = {
- ...selectedFile,
- qaPairs: selectedFile.qaPairs.map((qa) =>
- selectedQAs.includes(qa.id)
- ? { ...qa, status: "rejected" as const }
- : qa
- ),
- };
- setSelectedFile(updatedFile);
- setSelectedQAs([]);
-
- message({
- title: "批量操作完成",
- description: `已将 ${selectedQAs.length} 个QA对标记为不留用`,
- });
- }
- };
-
- const handleQASelect = (qaId: string, checked: boolean) => {
- if (checked) {
- setSelectedQAs([...selectedQAs, qaId]);
- } else {
- setSelectedQAs(selectedQAs.filter((id) => id !== qaId));
- }
- };
-
- const handleSelectAll = (checked: boolean) => {
- if (checked && selectedFile) {
- setSelectedQAs(selectedFile.qaPairs.map((qa) => qa.id));
- } else {
- setSelectedQAs([]);
- }
- };
-
- const getStatusBadge = (status: string) => {
- switch (status) {
- case "approved":
- return 留用;
- case "rejected":
- return 不留用;
- default:
- return 待标注;
- }
- };
-
- const getConfidenceColor = (confidence?: number) => {
- if (!confidence) return "text-gray-500";
- if (confidence >= 0.8) return "text-green-600";
- if (confidence >= 0.6) return "text-yellow-600";
- return "text-red-600";
- };
-
- const filteredQAs =
- selectedFile?.qaPairs.filter((qa) => {
- const matchesSearch =
- qa.question.toLowerCase().includes(searchQuery.toLowerCase()) ||
- qa.answer.toLowerCase().includes(searchQuery.toLowerCase());
- const matchesStatus =
- statusFilter === "all" || qa.status === statusFilter;
- return matchesSearch && matchesStatus;
- }) || [];
-
- return (
-
- {/* File List */}
-
-
-
-
-
-
- {mockFiles.map((file) => (
-
handleFileSelect(file)}
- >
-
-
-
-
{file.name}
-
- {file.qaPairs.length} 个QA对
-
-
-
-
- ))}
-
-
-
-
-
- {/* QA Annotation Area */}
-
- {selectedFile ? (
-
- {/* Header */}
-
-
-
{selectedFile.name}
-
- 共 {selectedFile.qaPairs.length} 个QA对
-
-
-
-
-
- 保存并下一个
-
- 跳过
-
-
-
- {/* Filters and Batch Actions */}
-
-
-
-
-
- setSearchQuery(e.target.value)}
- className="pl-10 w-64"
- />
-
-
-
-
- {selectedQAs.length > 0 && (
-
-
- 已选择 {selectedQAs.length} 个
-
-
-
- 批量留用
-
-
-
- 批量不留用
-
-
- )}
-
-
-
- {/* QA List */}
-
-
- 0
- }
- onChange={handleSelectAll}
- />
- 全选
-
-
-
-
- {filteredQAs.map((qa) => (
-
-
-
-
-
- handleQASelect(qa.id, checked as boolean)
- }
- />
-
-
- QA-{qa.id}
-
-
-
- {qa.confidence && (
-
- 置信度: {(qa.confidence * 100).toFixed(1)}%
-
- )}
- {getStatusBadge(qa.status)}
-
-
-
-
-
-
-
-
- 问题
-
-
-
- {qa.question}
-
-
-
-
-
-
-
- 答案
-
-
-
- {qa.answer}
-
-
-
-
-
-
- handleQAStatusChange(qa.id, "approved")
- }
- size="sm"
- variant={
- qa.status === "approved" ? "default" : "outline"
- }
- className={
- qa.status === "approved"
- ? "bg-green-600 hover:bg-green-700"
- : ""
- }
- >
-
- 留用
-
-
- handleQAStatusChange(qa.id, "rejected")
- }
- size="sm"
- variant={
- qa.status === "rejected"
- ? "destructive"
- : "outline"
- }
- >
-
- 不留用
-
-
-
-
- ))}
-
-
-
-
- ) : (
-
-
-
-
- 选择文件开始标注
-
-
- 从左侧文件列表中选择一个文件开始标注工作
-
-
-
- )}
-
-
- );
-}
+import { useState } from "react";
+import { Card, Button, Badge, Input, Checkbox } from "antd";
+
+import {
+ File,
+ Search,
+ CheckCircle,
+ ThumbsUp,
+ ThumbsDown,
+ MessageSquare,
+ HelpCircle,
+} from "lucide-react";
+
+interface QAPair {
+ id: string;
+ question: string;
+ answer: string;
+ status: "pending" | "approved" | "rejected";
+ confidence?: number;
+}
+
+interface FileData {
+ id: string;
+ name: string;
+ qaPairs: QAPair[];
+}
+
+interface TextAnnotationWorkspaceProps {
+ task: any;
+ currentFileIndex: number;
+ onSaveAndNext: () => void;
+ onSkipAndNext: () => void;
+}
+
+// 模拟文件数据
+const mockFiles: FileData[] = [
+ {
+ id: "1",
+ name: "document_001.txt",
+ qaPairs: [
+ {
+ id: "1",
+ question: "什么是人工智能?",
+ answer:
+ "人工智能(AI)是计算机科学的一个分支,致力于创建能够执行通常需要人类智能的任务的系统。",
+ status: "pending",
+ confidence: 0.85,
+ },
+ {
+ id: "2",
+ question: "机器学习和深度学习有什么区别?",
+ answer:
+ "机器学习是人工智能的一个子集,而深度学习是机器学习的一个子集。深度学习使用神经网络来模拟人脑的工作方式。",
+ status: "pending",
+ confidence: 0.92,
+ },
+ {
+ id: "3",
+ question: "什么是神经网络?",
+ answer:
+ "神经网络是一种受生物神经网络启发的计算模型,由相互连接的节点(神经元)组成,能够学习和识别模式。",
+ status: "pending",
+ confidence: 0.78,
+ },
+ ],
+ },
+ {
+ id: "2",
+ name: "document_002.txt",
+ qaPairs: [
+ {
+ id: "4",
+ question: "什么是自然语言处理?",
+ answer:
+ "自然语言处理(NLP)是人工智能的一个分支,专注于使计算机能够理解、解释和生成人类语言。",
+ status: "pending",
+ confidence: 0.88,
+ },
+ {
+ id: "5",
+ question: "计算机视觉的应用有哪些?",
+ answer:
+ "计算机视觉广泛应用于图像识别、人脸识别、自动驾驶、医学影像分析、安防监控等领域。",
+ status: "pending",
+ confidence: 0.91,
+ },
+ ],
+ },
+];
+
+export default function TextAnnotationWorkspace({
+ onSaveAndNext,
+ onSkipAndNext,
+}: TextAnnotationWorkspaceProps) {
+ const [selectedFile, setSelectedFile] = useState(
+ mockFiles[0]
+ );
+ const [searchQuery, setSearchQuery] = useState("");
+ const [statusFilter, setStatusFilter] = useState("all");
+ const [selectedQAs, setSelectedQAs] = useState([]);
+
+ const handleFileSelect = (file: FileData) => {
+ setSelectedFile(file);
+ setSelectedQAs([]);
+ };
+
+ const handleQAStatusChange = (
+ qaId: string,
+ status: "approved" | "rejected"
+ ) => {
+ if (selectedFile) {
+ const updatedFile = {
+ ...selectedFile,
+ qaPairs: selectedFile.qaPairs.map((qa) =>
+ qa.id === qaId ? { ...qa, status } : qa
+ ),
+ };
+ setSelectedFile(updatedFile);
+
+ message({
+ title: status === "approved" ? "已标记为留用" : "已标记为不留用",
+ description: `QA对 "${qaId}" 状态已更新`,
+ });
+ }
+ };
+
+ const handleBatchApprove = () => {
+ if (selectedFile && selectedQAs.length > 0) {
+ const updatedFile = {
+ ...selectedFile,
+ qaPairs: selectedFile.qaPairs.map((qa) =>
+ selectedQAs.includes(qa.id)
+ ? { ...qa, status: "approved" as const }
+ : qa
+ ),
+ };
+ setSelectedFile(updatedFile);
+ setSelectedQAs([]);
+
+ message({
+ title: "批量操作完成",
+ description: `已将 ${selectedQAs.length} 个QA对标记为留用`,
+ });
+ }
+ };
+
+ const handleBatchReject = () => {
+ if (selectedFile && selectedQAs.length > 0) {
+ const updatedFile = {
+ ...selectedFile,
+ qaPairs: selectedFile.qaPairs.map((qa) =>
+ selectedQAs.includes(qa.id)
+ ? { ...qa, status: "rejected" as const }
+ : qa
+ ),
+ };
+ setSelectedFile(updatedFile);
+ setSelectedQAs([]);
+
+ message({
+ title: "批量操作完成",
+ description: `已将 ${selectedQAs.length} 个QA对标记为不留用`,
+ });
+ }
+ };
+
+ const handleQASelect = (qaId: string, checked: boolean) => {
+ if (checked) {
+ setSelectedQAs([...selectedQAs, qaId]);
+ } else {
+ setSelectedQAs(selectedQAs.filter((id) => id !== qaId));
+ }
+ };
+
+ const handleSelectAll = (checked: boolean) => {
+ if (checked && selectedFile) {
+ setSelectedQAs(selectedFile.qaPairs.map((qa) => qa.id));
+ } else {
+ setSelectedQAs([]);
+ }
+ };
+
+ const getStatusBadge = (status: string) => {
+ switch (status) {
+ case "approved":
+ return 留用;
+ case "rejected":
+ return 不留用;
+ default:
+ return 待标注;
+ }
+ };
+
+ const getConfidenceColor = (confidence?: number) => {
+ if (!confidence) return "text-gray-500";
+ if (confidence >= 0.8) return "text-green-600";
+ if (confidence >= 0.6) return "text-yellow-600";
+ return "text-red-600";
+ };
+
+ const filteredQAs =
+ selectedFile?.qaPairs.filter((qa) => {
+ const matchesSearch =
+ qa.question.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ qa.answer.toLowerCase().includes(searchQuery.toLowerCase());
+ const matchesStatus =
+ statusFilter === "all" || qa.status === statusFilter;
+ return matchesSearch && matchesStatus;
+ }) || [];
+
+ return (
+
+ {/* File List */}
+
+
+
+
+
+
+ {mockFiles.map((file) => (
+
handleFileSelect(file)}
+ >
+
+
+
+
{file.name}
+
+ {file.qaPairs.length} 个QA对
+
+
+
+
+ ))}
+
+
+
+
+
+ {/* QA Annotation Area */}
+
+ {selectedFile ? (
+
+ {/* Header */}
+
+
+
{selectedFile.name}
+
+ 共 {selectedFile.qaPairs.length} 个QA对
+
+
+
+
+
+ 保存并下一个
+
+ 跳过
+
+
+
+ {/* Filters and Batch Actions */}
+
+
+
+
+
+ setSearchQuery(e.target.value)}
+ className="pl-10 w-64"
+ />
+
+
+
+
+ {selectedQAs.length > 0 && (
+
+
+ 已选择 {selectedQAs.length} 个
+
+
+
+ 批量留用
+
+
+
+ 批量不留用
+
+
+ )}
+
+
+
+ {/* QA List */}
+
+
+ 0
+ }
+ onChange={handleSelectAll}
+ />
+ 全选
+
+
+
+
+ {filteredQAs.map((qa) => (
+
+
+
+
+
+ handleQASelect(qa.id, checked as boolean)
+ }
+ />
+
+
+ QA-{qa.id}
+
+
+
+ {qa.confidence && (
+
+ 置信度: {(qa.confidence * 100).toFixed(1)}%
+
+ )}
+ {getStatusBadge(qa.status)}
+
+
+
+
+
+
+
+
+ 问题
+
+
+
+ {qa.question}
+
+
+
+
+
+
+
+ 答案
+
+
+
+ {qa.answer}
+
+
+
+
+
+
+ handleQAStatusChange(qa.id, "approved")
+ }
+ size="sm"
+ variant={
+ qa.status === "approved" ? "default" : "outline"
+ }
+ className={
+ qa.status === "approved"
+ ? "bg-green-600 hover:bg-green-700"
+ : ""
+ }
+ >
+
+ 留用
+
+
+ handleQAStatusChange(qa.id, "rejected")
+ }
+ size="sm"
+ variant={
+ qa.status === "rejected"
+ ? "destructive"
+ : "outline"
+ }
+ >
+
+ 不留用
+
+
+
+
+ ))}
+
+
+
+
+ ) : (
+
+
+
+
+ 选择文件开始标注
+
+
+ 从左侧文件列表中选择一个文件开始标注工作
+
+
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/pages/DataAnnotation/Annotate/components/VideoAnnotation.tsx b/frontend/src/pages/DataAnnotation/Annotate/components/VideoAnnotation.tsx
index f5bc3e60b..e0f112c3f 100644
--- a/frontend/src/pages/DataAnnotation/Annotate/components/VideoAnnotation.tsx
+++ b/frontend/src/pages/DataAnnotation/Annotate/components/VideoAnnotation.tsx
@@ -1,688 +1,688 @@
-
-
-import type React from "react";
-
-import { useState, useRef, useEffect } from "react";
-import { Card, Button, Badge, Slider, message } from "antd";
-import {
- Play,
- Pause,
- Square,
- SkipBack,
- SkipForward,
- Volume2,
- VolumeX,
- MousePointer,
- CheckCircle,
- Trash2,
- Eye,
- EyeOff,
- Target,
- Maximize,
-} from "lucide-react";
-
-interface VideoAnnotation {
- id: string;
- frameTime: number;
- type: "rectangle" | "point" | "polygon";
- coordinates: number[];
- label: string;
- color: string;
- trackId?: string;
- visible: boolean;
-}
-
-interface VideoTrack {
- id: string;
- label: string;
- color: string;
- annotations: VideoAnnotation[];
- startTime: number;
- endTime: number;
-}
-
-interface VideoAnnotationWorkspaceProps {
- task: any;
- currentFileIndex: number;
- onSaveAndNext: () => void;
- onSkipAndNext: () => void;
-}
-
-// 模拟视频数据
-const mockVideoFiles = [
- {
- id: "1",
- name: "traffic_scene_001.mp4",
- url: "/placeholder-video.mp4", // 这里应该是实际的视频文件URL
- duration: 120, // 2分钟
- fps: 30,
- width: 1920,
- height: 1080,
- },
-];
-
-// 预定义标签
-const videoLabels = [
- { name: "车辆", color: "#3B82F6" },
- { name: "行人", color: "#10B981" },
- { name: "自行车", color: "#F59E0B" },
- { name: "交通灯", color: "#EF4444" },
- { name: "路标", color: "#8B5CF6" },
- { name: "其他", color: "#6B7280" },
-];
-
-export default function VideoAnnotationWorkspace({
- task,
- currentFileIndex,
- onSaveAndNext,
- onSkipAndNext,
-}: VideoAnnotationWorkspaceProps) {
- const [messageApi, contextHolder] = message.useMessage();
- const videoRef = useRef(null);
- const canvasRef = useRef(null);
- const [currentVideo] = useState(mockVideoFiles[0]);
- const [tracks, setTracks] = useState([]);
- const [isPlaying, setIsPlaying] = useState(false);
- const [currentTime, setCurrentTime] = useState(0);
- const [duration, setDuration] = useState(currentVideo.duration);
- const [volume, setVolume] = useState(1);
- const [isMuted, setIsMuted] = useState(false);
- const [selectedTool, setSelectedTool] = useState<
- "select" | "rectangle" | "point"
- >("select");
- const [selectedLabel, setSelectedLabel] = useState(videoLabels[0]);
- const [selectedTrack, setSelectedTrack] = useState(null);
- const [isDrawing, setIsDrawing] = useState(false);
- const [startPoint, setStartPoint] = useState({ x: 0, y: 0 });
- const [playbackSpeed, setPlaybackSpeed] = useState(1);
- const [isFullscreen, setIsFullscreen] = useState(false);
-
- useEffect(() => {
- const video = videoRef.current;
- if (!video) return;
-
- const updateTime = () => setCurrentTime(video.currentTime);
- const updateDuration = () => setDuration(video.duration);
- const handleEnded = () => setIsPlaying(false);
-
- video.addEventListener("timeupdate", updateTime);
- video.addEventListener("loadedmetadata", updateDuration);
- video.addEventListener("ended", handleEnded);
-
- return () => {
- video.removeEventListener("timeupdate", updateTime);
- video.removeEventListener("loadedmetadata", updateDuration);
- video.removeEventListener("ended", handleEnded);
- };
- }, []);
-
- useEffect(() => {
- drawCanvas();
- }, [currentTime, tracks, selectedTrack]);
-
- const drawCanvas = () => {
- const canvas = canvasRef.current;
- const video = videoRef.current;
- if (!canvas || !video) return;
-
- const ctx = canvas.getContext("2d");
- if (!ctx) return;
-
- // 清空画布
- ctx.clearRect(0, 0, canvas.width, canvas.height);
-
- // 绘制当前帧的标注
- tracks.forEach((track) => {
- if (!track.annotations.length) return;
-
- // 找到当前时间最近的标注
- const currentAnnotation = track.annotations
- .filter((ann) => Math.abs(ann.frameTime - currentTime) < 0.1)
- .sort(
- (a, b) =>
- Math.abs(a.frameTime - currentTime) -
- Math.abs(b.frameTime - currentTime)
- )[0];
-
- if (!currentAnnotation || !currentAnnotation.visible) return;
-
- ctx.strokeStyle = track.color;
- ctx.fillStyle = track.color + "20";
- ctx.lineWidth = selectedTrack === track.id ? 3 : 2;
-
- if (currentAnnotation.type === "rectangle") {
- const [x, y, width, height] = currentAnnotation.coordinates;
- ctx.strokeRect(x, y, width, height);
- ctx.fillRect(x, y, width, height);
-
- // 绘制标签
- ctx.fillStyle = track.color;
- ctx.fillRect(x, y - 20, ctx.measureText(track.label).width + 8, 20);
- ctx.fillStyle = "white";
- ctx.font = "12px Arial";
- ctx.fillText(track.label, x + 4, y - 6);
- } else if (currentAnnotation.type === "point") {
- const [x, y] = currentAnnotation.coordinates;
- ctx.beginPath();
- ctx.arc(x, y, 5, 0, 2 * Math.PI);
- ctx.fill();
- ctx.stroke();
-
- // 绘制标签
- ctx.fillStyle = track.color;
- ctx.fillRect(
- x + 10,
- y - 10,
- ctx.measureText(track.label).width + 8,
- 20
- );
- ctx.fillStyle = "white";
- ctx.font = "12px Arial";
- ctx.fillText(track.label, x + 14, y + 4);
- }
- });
- };
-
- const togglePlayPause = () => {
- const video = videoRef.current;
- if (!video) return;
-
- if (isPlaying) {
- video.pause();
- } else {
- video.play();
- }
- setIsPlaying(!isPlaying);
- };
-
- const handleSeek = (time: number) => {
- const video = videoRef.current;
- if (!video) return;
-
- video.currentTime = time;
- setCurrentTime(time);
- };
-
- const handleVolumeChange = (value: number[]) => {
- const video = videoRef.current;
- if (!video) return;
-
- const newVolume = value[0];
- video.volume = newVolume;
- setVolume(newVolume);
- setIsMuted(newVolume === 0);
- };
-
- const toggleMute = () => {
- const video = videoRef.current;
- if (!video) return;
-
- if (isMuted) {
- video.volume = volume;
- setIsMuted(false);
- } else {
- video.volume = 0;
- setIsMuted(true);
- }
- };
-
- const handleSpeedChange = (speed: number) => {
- const video = videoRef.current;
- if (!video) return;
-
- video.playbackRate = speed;
- setPlaybackSpeed(speed);
- };
-
- const handleCanvasMouseDown = (e: React.MouseEvent) => {
- if (selectedTool === "select") return;
-
- const canvas = canvasRef.current;
- if (!canvas) return;
-
- const rect = canvas.getBoundingClientRect();
- const x = e.clientX - rect.left;
- const y = e.clientY - rect.top;
-
- if (selectedTool === "point") {
- createPointAnnotation(x, y);
- } else if (selectedTool === "rectangle") {
- setIsDrawing(true);
- setStartPoint({ x, y });
- }
- };
-
- const handleCanvasMouseMove = (e: React.MouseEvent) => {
- if (!isDrawing || selectedTool !== "rectangle") return;
-
- const canvas = canvasRef.current;
- if (!canvas) return;
-
- const rect = canvas.getBoundingClientRect();
- const x = e.clientX - rect.left;
- const y = e.clientY - rect.top;
-
- // 实时预览
- drawCanvas();
- const ctx = canvas.getContext("2d");
- if (!ctx) return;
-
- ctx.strokeStyle = selectedLabel.color;
- ctx.lineWidth = 2;
- ctx.setLineDash([5, 5]);
- ctx.strokeRect(
- startPoint.x,
- startPoint.y,
- x - startPoint.x,
- y - startPoint.y
- );
- ctx.setLineDash([]);
- };
-
- const handleCanvasMouseUp = (e: React.MouseEvent) => {
- if (!isDrawing || selectedTool !== "rectangle") return;
-
- const canvas = canvasRef.current;
- if (!canvas) return;
-
- const rect = canvas.getBoundingClientRect();
- const x = e.clientX - rect.left;
- const y = e.clientY - rect.top;
-
- const width = x - startPoint.x;
- const height = y - startPoint.y;
-
- if (Math.abs(width) > 10 && Math.abs(height) > 10) {
- createRectangleAnnotation(startPoint.x, startPoint.y, width, height);
- }
-
- setIsDrawing(false);
- };
-
- const createPointAnnotation = (x: number, y: number) => {
- const newAnnotation: VideoAnnotation = {
- id: Date.now().toString(),
- frameTime: currentTime,
- type: "point",
- coordinates: [x, y],
- label: selectedLabel.name,
- color: selectedLabel.color,
- visible: true,
- };
-
- const newTrack: VideoTrack = {
- id: Date.now().toString(),
- label: selectedLabel.name,
- color: selectedLabel.color,
- annotations: [newAnnotation],
- startTime: currentTime,
- endTime: currentTime,
- };
-
- setTracks([...tracks, newTrack]);
- messageApi({
- title: "点标注已添加",
- description: `在时间 ${formatTime(currentTime)} 添加了点标注`,
- });
- };
-
- const createRectangleAnnotation = (
- x: number,
- y: number,
- width: number,
- height: number
- ) => {
- const newAnnotation: VideoAnnotation = {
- id: Date.now().toString(),
- frameTime: currentTime,
- type: "rectangle",
- coordinates: [x, y, width, height],
- label: selectedLabel.name,
- color: selectedLabel.color,
- visible: true,
- };
-
- const newTrack: VideoTrack = {
- id: Date.now().toString(),
- label: selectedLabel.name,
- color: selectedLabel.color,
- annotations: [newAnnotation],
- startTime: currentTime,
- endTime: currentTime,
- };
-
- setTracks([...tracks, newTrack]);
- messageApi.success(`在时间 ${formatTime(currentTime)} 添加了矩形标注`);
- };
-
- const deleteTrack = (trackId: string) => {
- setTracks(tracks.filter((t) => t.id !== trackId));
- setSelectedTrack(null);
- messageApi.success("标注轨迹已被删除");
- };
-
- const toggleTrackVisibility = (trackId: string) => {
- setTracks(
- tracks.map((track) =>
- track.id === trackId
- ? {
- ...track,
- annotations: track.annotations.map((ann) => ({
- ...ann,
- visible: !ann.visible,
- })),
- }
- : track
- )
- );
- };
-
- const formatTime = (seconds: number) => {
- const mins = Math.floor(seconds / 60);
- const secs = Math.floor(seconds % 60);
- return `${mins.toString().padStart(2, "0")}:${secs
- .toString()
- .padStart(2, "0")}`;
- };
-
- const toggleFullscreen = () => {
- const video = videoRef.current;
- if (!video) return;
-
- if (!isFullscreen) {
- if (video.requestFullscreen) {
- video.requestFullscreen();
- }
- } else {
- if (document.exitFullscreen) {
- document.exitFullscreen();
- }
- }
- setIsFullscreen(!isFullscreen);
- };
-
- return (
-
- {/* Tools Panel */}
-
- {/* Tool Selection */}
-
-
- 工具
-
-
- setSelectedTool("select")}
- >
-
- 选择
-
- setSelectedTool("rectangle")}
- >
-
- 矩形
-
- setSelectedTool("point")}
- >
-
- 点标注
-
-
-
-
- {/* Labels */}
-
-
- 标签
-
-
- {videoLabels.map((label) => (
- setSelectedLabel(label)}
- >
-
- {label.name}
-
- ))}
-
-
-
- {/* Playback Speed */}
-
-
- 播放速度
-
-
- {[0.25, 0.5, 1, 1.5, 2].map((speed) => (
- handleSpeedChange(speed)}
- >
- {speed}x
-
- ))}
-
-
-
- {/* Tracks List */}
-
-
- 标注轨迹
-
-
-
-
- {tracks.map((track) => (
-
setSelectedTrack(track.id)}
- >
-
-
-
- {
- e.stopPropagation();
- toggleTrackVisibility(track.id);
- }}
- >
- {track.annotations[0]?.visible ? (
-
- ) : (
-
- )}
-
- {
- e.stopPropagation();
- deleteTrack(track.id);
- }}
- >
-
-
-
-
-
- {track.annotations.length} 个关键帧
-
-
- ))}
- {tracks.length === 0 && (
-
- 暂无轨迹
-
- )}
-
-
-
-
-
-
- {/* Video Player and Canvas */}
-
- {/* Video Container */}
-
-
-
-
- {/* Video Info Overlay */}
-
- {currentVideo.name} | {formatTime(currentTime)} /{" "}
- {formatTime(duration)}
-
-
- {/* Tool Info Overlay */}
-
- {selectedTool === "select"
- ? "选择模式"
- : selectedTool === "rectangle"
- ? "矩形标注"
- : "点标注"}{" "}
- | {selectedLabel.name}
-
-
-
- {/* Video Controls */}
-
- {/* Timeline */}
-
-
- {formatTime(currentTime)}
- {formatTime(duration)}
-
-
handleSeek(value[0])}
- className="w-full"
- />
-
-
- {/* Player Controls */}
-
-
-
handleSeek(Math.max(0, currentTime - 10))}
- >
-
-
-
- {isPlaying ? (
-
- ) : (
-
- )}
-
-
handleSeek(Math.min(duration, currentTime + 10))}
- >
-
-
-
- {/* Volume Control */}
-
-
- {isMuted ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
-
-
-
- {playbackSpeed}x
- {tracks.length} 轨迹
-
- 跳过
-
-
-
- 保存并下一个
-
-
-
-
-
-
- );
-}
+
+
+import type React from "react";
+
+import { useState, useRef, useEffect } from "react";
+import { Card, Button, Badge, Slider, message } from "antd";
+import {
+ Play,
+ Pause,
+ Square,
+ SkipBack,
+ SkipForward,
+ Volume2,
+ VolumeX,
+ MousePointer,
+ CheckCircle,
+ Trash2,
+ Eye,
+ EyeOff,
+ Target,
+ Maximize,
+} from "lucide-react";
+
+interface VideoAnnotation {
+ id: string;
+ frameTime: number;
+ type: "rectangle" | "point" | "polygon";
+ coordinates: number[];
+ label: string;
+ color: string;
+ trackId?: string;
+ visible: boolean;
+}
+
+interface VideoTrack {
+ id: string;
+ label: string;
+ color: string;
+ annotations: VideoAnnotation[];
+ startTime: number;
+ endTime: number;
+}
+
+interface VideoAnnotationWorkspaceProps {
+ task: any;
+ currentFileIndex: number;
+ onSaveAndNext: () => void;
+ onSkipAndNext: () => void;
+}
+
+// 模拟视频数据
+const mockVideoFiles = [
+ {
+ id: "1",
+ name: "traffic_scene_001.mp4",
+ url: "/placeholder-video.mp4", // 这里应该是实际的视频文件URL
+ duration: 120, // 2分钟
+ fps: 30,
+ width: 1920,
+ height: 1080,
+ },
+];
+
+// 预定义标签
+const videoLabels = [
+ { name: "车辆", color: "#3B82F6" },
+ { name: "行人", color: "#10B981" },
+ { name: "自行车", color: "#F59E0B" },
+ { name: "交通灯", color: "#EF4444" },
+ { name: "路标", color: "#8B5CF6" },
+ { name: "其他", color: "#6B7280" },
+];
+
+export default function VideoAnnotationWorkspace({
+ task,
+ currentFileIndex,
+ onSaveAndNext,
+ onSkipAndNext,
+}: VideoAnnotationWorkspaceProps) {
+ const [messageApi, contextHolder] = message.useMessage();
+ const videoRef = useRef(null);
+ const canvasRef = useRef(null);
+ const [currentVideo] = useState(mockVideoFiles[0]);
+ const [tracks, setTracks] = useState([]);
+ const [isPlaying, setIsPlaying] = useState(false);
+ const [currentTime, setCurrentTime] = useState(0);
+ const [duration, setDuration] = useState(currentVideo.duration);
+ const [volume, setVolume] = useState(1);
+ const [isMuted, setIsMuted] = useState(false);
+ const [selectedTool, setSelectedTool] = useState<
+ "select" | "rectangle" | "point"
+ >("select");
+ const [selectedLabel, setSelectedLabel] = useState(videoLabels[0]);
+ const [selectedTrack, setSelectedTrack] = useState(null);
+ const [isDrawing, setIsDrawing] = useState(false);
+ const [startPoint, setStartPoint] = useState({ x: 0, y: 0 });
+ const [playbackSpeed, setPlaybackSpeed] = useState(1);
+ const [isFullscreen, setIsFullscreen] = useState(false);
+
+ useEffect(() => {
+ const video = videoRef.current;
+ if (!video) return;
+
+ const updateTime = () => setCurrentTime(video.currentTime);
+ const updateDuration = () => setDuration(video.duration);
+ const handleEnded = () => setIsPlaying(false);
+
+ video.addEventListener("timeupdate", updateTime);
+ video.addEventListener("loadedmetadata", updateDuration);
+ video.addEventListener("ended", handleEnded);
+
+ return () => {
+ video.removeEventListener("timeupdate", updateTime);
+ video.removeEventListener("loadedmetadata", updateDuration);
+ video.removeEventListener("ended", handleEnded);
+ };
+ }, []);
+
+ useEffect(() => {
+ drawCanvas();
+ }, [currentTime, tracks, selectedTrack]);
+
+ const drawCanvas = () => {
+ const canvas = canvasRef.current;
+ const video = videoRef.current;
+ if (!canvas || !video) return;
+
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return;
+
+ // 清空画布
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ // 绘制当前帧的标注
+ tracks.forEach((track) => {
+ if (!track.annotations.length) return;
+
+ // 找到当前时间最近的标注
+ const currentAnnotation = track.annotations
+ .filter((ann) => Math.abs(ann.frameTime - currentTime) < 0.1)
+ .sort(
+ (a, b) =>
+ Math.abs(a.frameTime - currentTime) -
+ Math.abs(b.frameTime - currentTime)
+ )[0];
+
+ if (!currentAnnotation || !currentAnnotation.visible) return;
+
+ ctx.strokeStyle = track.color;
+ ctx.fillStyle = track.color + "20";
+ ctx.lineWidth = selectedTrack === track.id ? 3 : 2;
+
+ if (currentAnnotation.type === "rectangle") {
+ const [x, y, width, height] = currentAnnotation.coordinates;
+ ctx.strokeRect(x, y, width, height);
+ ctx.fillRect(x, y, width, height);
+
+ // 绘制标签
+ ctx.fillStyle = track.color;
+ ctx.fillRect(x, y - 20, ctx.measureText(track.label).width + 8, 20);
+ ctx.fillStyle = "white";
+ ctx.font = "12px Arial";
+ ctx.fillText(track.label, x + 4, y - 6);
+ } else if (currentAnnotation.type === "point") {
+ const [x, y] = currentAnnotation.coordinates;
+ ctx.beginPath();
+ ctx.arc(x, y, 5, 0, 2 * Math.PI);
+ ctx.fill();
+ ctx.stroke();
+
+ // 绘制标签
+ ctx.fillStyle = track.color;
+ ctx.fillRect(
+ x + 10,
+ y - 10,
+ ctx.measureText(track.label).width + 8,
+ 20
+ );
+ ctx.fillStyle = "white";
+ ctx.font = "12px Arial";
+ ctx.fillText(track.label, x + 14, y + 4);
+ }
+ });
+ };
+
+ const togglePlayPause = () => {
+ const video = videoRef.current;
+ if (!video) return;
+
+ if (isPlaying) {
+ video.pause();
+ } else {
+ video.play();
+ }
+ setIsPlaying(!isPlaying);
+ };
+
+ const handleSeek = (time: number) => {
+ const video = videoRef.current;
+ if (!video) return;
+
+ video.currentTime = time;
+ setCurrentTime(time);
+ };
+
+ const handleVolumeChange = (value: number[]) => {
+ const video = videoRef.current;
+ if (!video) return;
+
+ const newVolume = value[0];
+ video.volume = newVolume;
+ setVolume(newVolume);
+ setIsMuted(newVolume === 0);
+ };
+
+ const toggleMute = () => {
+ const video = videoRef.current;
+ if (!video) return;
+
+ if (isMuted) {
+ video.volume = volume;
+ setIsMuted(false);
+ } else {
+ video.volume = 0;
+ setIsMuted(true);
+ }
+ };
+
+ const handleSpeedChange = (speed: number) => {
+ const video = videoRef.current;
+ if (!video) return;
+
+ video.playbackRate = speed;
+ setPlaybackSpeed(speed);
+ };
+
+ const handleCanvasMouseDown = (e: React.MouseEvent) => {
+ if (selectedTool === "select") return;
+
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+
+ const rect = canvas.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+
+ if (selectedTool === "point") {
+ createPointAnnotation(x, y);
+ } else if (selectedTool === "rectangle") {
+ setIsDrawing(true);
+ setStartPoint({ x, y });
+ }
+ };
+
+ const handleCanvasMouseMove = (e: React.MouseEvent) => {
+ if (!isDrawing || selectedTool !== "rectangle") return;
+
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+
+ const rect = canvas.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+
+ // 实时预览
+ drawCanvas();
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return;
+
+ ctx.strokeStyle = selectedLabel.color;
+ ctx.lineWidth = 2;
+ ctx.setLineDash([5, 5]);
+ ctx.strokeRect(
+ startPoint.x,
+ startPoint.y,
+ x - startPoint.x,
+ y - startPoint.y
+ );
+ ctx.setLineDash([]);
+ };
+
+ const handleCanvasMouseUp = (e: React.MouseEvent) => {
+ if (!isDrawing || selectedTool !== "rectangle") return;
+
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+
+ const rect = canvas.getBoundingClientRect();
+ const x = e.clientX - rect.left;
+ const y = e.clientY - rect.top;
+
+ const width = x - startPoint.x;
+ const height = y - startPoint.y;
+
+ if (Math.abs(width) > 10 && Math.abs(height) > 10) {
+ createRectangleAnnotation(startPoint.x, startPoint.y, width, height);
+ }
+
+ setIsDrawing(false);
+ };
+
+ const createPointAnnotation = (x: number, y: number) => {
+ const newAnnotation: VideoAnnotation = {
+ id: Date.now().toString(),
+ frameTime: currentTime,
+ type: "point",
+ coordinates: [x, y],
+ label: selectedLabel.name,
+ color: selectedLabel.color,
+ visible: true,
+ };
+
+ const newTrack: VideoTrack = {
+ id: Date.now().toString(),
+ label: selectedLabel.name,
+ color: selectedLabel.color,
+ annotations: [newAnnotation],
+ startTime: currentTime,
+ endTime: currentTime,
+ };
+
+ setTracks([...tracks, newTrack]);
+ messageApi({
+ title: "点标注已添加",
+ description: `在时间 ${formatTime(currentTime)} 添加了点标注`,
+ });
+ };
+
+ const createRectangleAnnotation = (
+ x: number,
+ y: number,
+ width: number,
+ height: number
+ ) => {
+ const newAnnotation: VideoAnnotation = {
+ id: Date.now().toString(),
+ frameTime: currentTime,
+ type: "rectangle",
+ coordinates: [x, y, width, height],
+ label: selectedLabel.name,
+ color: selectedLabel.color,
+ visible: true,
+ };
+
+ const newTrack: VideoTrack = {
+ id: Date.now().toString(),
+ label: selectedLabel.name,
+ color: selectedLabel.color,
+ annotations: [newAnnotation],
+ startTime: currentTime,
+ endTime: currentTime,
+ };
+
+ setTracks([...tracks, newTrack]);
+ messageApi.success(`在时间 ${formatTime(currentTime)} 添加了矩形标注`);
+ };
+
+ const deleteTrack = (trackId: string) => {
+ setTracks(tracks.filter((t) => t.id !== trackId));
+ setSelectedTrack(null);
+ messageApi.success("标注轨迹已被删除");
+ };
+
+ const toggleTrackVisibility = (trackId: string) => {
+ setTracks(
+ tracks.map((track) =>
+ track.id === trackId
+ ? {
+ ...track,
+ annotations: track.annotations.map((ann) => ({
+ ...ann,
+ visible: !ann.visible,
+ })),
+ }
+ : track
+ )
+ );
+ };
+
+ const formatTime = (seconds: number) => {
+ const mins = Math.floor(seconds / 60);
+ const secs = Math.floor(seconds % 60);
+ return `${mins.toString().padStart(2, "0")}:${secs
+ .toString()
+ .padStart(2, "0")}`;
+ };
+
+ const toggleFullscreen = () => {
+ const video = videoRef.current;
+ if (!video) return;
+
+ if (!isFullscreen) {
+ if (video.requestFullscreen) {
+ video.requestFullscreen();
+ }
+ } else {
+ if (document.exitFullscreen) {
+ document.exitFullscreen();
+ }
+ }
+ setIsFullscreen(!isFullscreen);
+ };
+
+ return (
+
+ {/* Tools Panel */}
+
+ {/* Tool Selection */}
+
+
+ 工具
+
+
+ setSelectedTool("select")}
+ >
+
+ 选择
+
+ setSelectedTool("rectangle")}
+ >
+
+ 矩形
+
+ setSelectedTool("point")}
+ >
+
+ 点标注
+
+
+
+
+ {/* Labels */}
+
+
+ 标签
+
+
+ {videoLabels.map((label) => (
+ setSelectedLabel(label)}
+ >
+
+ {label.name}
+
+ ))}
+
+
+
+ {/* Playback Speed */}
+
+
+ 播放速度
+
+
+ {[0.25, 0.5, 1, 1.5, 2].map((speed) => (
+ handleSpeedChange(speed)}
+ >
+ {speed}x
+
+ ))}
+
+
+
+ {/* Tracks List */}
+
+
+ 标注轨迹
+
+
+
+
+ {tracks.map((track) => (
+
setSelectedTrack(track.id)}
+ >
+
+
+
+ {
+ e.stopPropagation();
+ toggleTrackVisibility(track.id);
+ }}
+ >
+ {track.annotations[0]?.visible ? (
+
+ ) : (
+
+ )}
+
+ {
+ e.stopPropagation();
+ deleteTrack(track.id);
+ }}
+ >
+
+
+
+
+
+ {track.annotations.length} 个关键帧
+
+
+ ))}
+ {tracks.length === 0 && (
+
+ 暂无轨迹
+
+ )}
+
+
+
+
+
+
+ {/* Video Player and Canvas */}
+
+ {/* Video Container */}
+
+
+
+
+ {/* Video Info Overlay */}
+
+ {currentVideo.name} | {formatTime(currentTime)} /{" "}
+ {formatTime(duration)}
+
+
+ {/* Tool Info Overlay */}
+
+ {selectedTool === "select"
+ ? "选择模式"
+ : selectedTool === "rectangle"
+ ? "矩形标注"
+ : "点标注"}{" "}
+ | {selectedLabel.name}
+
+
+
+ {/* Video Controls */}
+
+ {/* Timeline */}
+
+
+ {formatTime(currentTime)}
+ {formatTime(duration)}
+
+
handleSeek(value[0])}
+ className="w-full"
+ />
+
+
+ {/* Player Controls */}
+
+
+
handleSeek(Math.max(0, currentTime - 10))}
+ >
+
+
+
+ {isPlaying ? (
+
+ ) : (
+
+ )}
+
+
handleSeek(Math.min(duration, currentTime + 10))}
+ >
+
+
+
+ {/* Volume Control */}
+
+
+ {isMuted ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {playbackSpeed}x
+ {tracks.length} 轨迹
+
+ 跳过
+
+
+
+ 保存并下一个
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/DataAnnotation/Create/CreateTask.tsx b/frontend/src/pages/DataAnnotation/Create/CreateTask.tsx
index d66b13e71..8a0f8da5c 100644
--- a/frontend/src/pages/DataAnnotation/Create/CreateTask.tsx
+++ b/frontend/src/pages/DataAnnotation/Create/CreateTask.tsx
@@ -1,346 +1,346 @@
-import type React from "react";
-import { useEffect, useState } from "react";
-import { Card, Button, Input, Select, Divider, Form, message } from "antd";
-import TextArea from "antd/es/input/TextArea";
-import {
- DatabaseOutlined,
- CheckOutlined,
- PlusOutlined,
-} from "@ant-design/icons";
-import { mockTemplates } from "@/mock/annotation";
-import CustomTemplateDialog from "./components/CustomTemplateDialog";
-import { Link, useNavigate } from "react-router";
-import { ArrowLeft } from "lucide-react";
-import { queryDatasetsUsingGet } from "../../DataManagement/dataset.api";
-import {
- DatasetType,
- type Dataset,
-} from "@/pages/DataManagement/dataset.model";
-
-interface Template {
- id: string;
- name: string;
- category: string;
- description: string;
- type: "text" | "image";
- preview?: string;
- icon: React.ReactNode;
- isCustom?: boolean;
-}
-
-const templateCategories = ["Computer Vision", "Natural Language Processing"];
-
-export default function AnnotationTaskCreate() {
- const navigate = useNavigate();
- const [form] = Form.useForm();
- const [showCustomTemplateDialog, setShowCustomTemplateDialog] =
- useState(false);
- const [selectedCategory, setSelectedCategory] = useState("Computer Vision");
- const [searchQuery, setSearchQuery] = useState("");
- const [datasetFilter, setDatasetFilter] = useState("all");
- const [selectedTemplate, setSelectedTemplate] = useState(
- null
- );
- const [datasets, setDatasets] = useState([]);
- const [selectedDataset, setSelectedDataset] = useState(null);
-
- // 用于Form的受控数据
- const [formValues, setFormValues] = useState({
- name: "",
- description: "",
- datasetId: "",
- templateId: "",
- });
-
- const fetchDatasets = async () => {
- const { data } = await queryDatasetsUsingGet();
- setDatasets(data.results || []);
- };
-
- useEffect(() => {
- fetchDatasets();
- }, []);
-
- const filteredTemplates = mockTemplates.filter(
- (template) => template.category === selectedCategory
- );
-
- const handleDatasetSelect = (datasetId: string) => {
- const dataset = datasets.find((ds) => ds.id === datasetId) || null;
- setSelectedDataset(dataset);
- setFormValues((prev) => ({ ...prev, datasetId }));
- if (dataset?.type === DatasetType.PRETRAIN_IMAGE) {
- setSelectedCategory("Computer Vision");
- } else if (dataset?.type === DatasetType.PRETRAIN_TEXT) {
- setSelectedCategory("Natural Language Processing");
- }
- setSelectedTemplate(null);
- setFormValues((prev) => ({ ...prev, templateId: "" }));
- };
-
- const handleTemplateSelect = (template: Template) => {
- setSelectedTemplate(template);
- setFormValues((prev) => ({ ...prev, templateId: template.id }));
- };
-
- const handleValuesChange = (_, allValues) => {
- setFormValues({ ...formValues, ...allValues });
- };
-
- const handleSubmit = async () => {
- try {
- const values = await form.validateFields();
- const dataset = datasets.find((ds) => ds.id === values.datasetId);
- const template = mockTemplates.find(
- (tpl) => tpl.id === values.templateId
- );
- if (!dataset) {
- message.error("请选择数据集");
- return;
- }
- if (!template) {
- message.error("请选择标注模板");
- return;
- }
- const taskData = {
- name: values.name,
- description: values.description,
- dataset,
- template,
- };
- // onCreateTask(taskData); // 实际创建逻辑
- message.success("标注任务创建成功");
- navigate("/data/annotation");
- } catch (e) {
- // 校验失败
- }
- };
-
- const handleSaveCustomTemplate = (templateData: any) => {
- setSelectedTemplate(templateData);
- setFormValues((prev) => ({ ...prev, templateId: templateData.id }));
- message.success(`自定义模板 "${templateData.name}" 已创建`);
- };
-
- return (
-
- {/* Header */}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* 模板选择 */}
-
- 模板选择
-
-
-
- {/* Category Sidebar */}
-
-
- {templateCategories.map((category) => {
- const isAvailable =
- selectedDataset?.type === "image"
- ? category === "Computer Vision"
- : category === "Natural Language Processing";
- return (
-
- isAvailable && setSelectedCategory(category)
- }
- style={{ textAlign: "left", marginBottom: 8 }}
- >
- {category}
-
- );
- })}
- }
- onClick={() => setShowCustomTemplateDialog(true)}
- >
- 自定义模板
-
-
-
- {/* Template Grid */}
-
-
-
- {filteredTemplates.map((template) => (
-
handleTemplateSelect(template)}
- >
- {template.preview && (
-
-

-
- )}
-
-
-
- {template.icon}
-
- {template.name}
-
-
-
-
- {template.description}
-
-
-
- ))}
- {/* Custom Template Option */}
-
setShowCustomTemplateDialog(true)}
- >
-
-
-
-
- {selectedTemplate?.isCustom && (
-
- )}
-
-
- 创建符合特定需求的标注模板
-
-
-
-
-
-
-
- {selectedTemplate && (
-
-
-
- 已选择模板
-
-
-
- {selectedTemplate.name} - {selectedTemplate.description}
-
-
- )}
-
-
-
-
- navigate("/data/annotation")}>取消
-
- 创建任务
-
-
-
-
- {/* Custom Template Dialog */}
-
-
- );
-}
+import type React from "react";
+import { useEffect, useState } from "react";
+import { Card, Button, Input, Select, Divider, Form, message } from "antd";
+import TextArea from "antd/es/input/TextArea";
+import {
+ DatabaseOutlined,
+ CheckOutlined,
+ PlusOutlined,
+} from "@ant-design/icons";
+import { mockTemplates } from "@/mock/annotation";
+import CustomTemplateDialog from "./components/CustomTemplateDialog";
+import { Link, useNavigate } from "react-router";
+import { ArrowLeft } from "lucide-react";
+import { queryDatasetsUsingGet } from "../../DataManagement/dataset.api";
+import {
+ DatasetType,
+ type Dataset,
+} from "@/pages/DataManagement/dataset.model";
+
+interface Template {
+ id: string;
+ name: string;
+ category: string;
+ description: string;
+ type: "text" | "image";
+ preview?: string;
+ icon: React.ReactNode;
+ isCustom?: boolean;
+}
+
+const templateCategories = ["Computer Vision", "Natural Language Processing"];
+
+export default function AnnotationTaskCreate() {
+ const navigate = useNavigate();
+ const [form] = Form.useForm();
+ const [showCustomTemplateDialog, setShowCustomTemplateDialog] =
+ useState(false);
+ const [selectedCategory, setSelectedCategory] = useState("Computer Vision");
+ const [searchQuery, setSearchQuery] = useState("");
+ const [datasetFilter, setDatasetFilter] = useState("all");
+ const [selectedTemplate, setSelectedTemplate] = useState(
+ null
+ );
+ const [datasets, setDatasets] = useState([]);
+ const [selectedDataset, setSelectedDataset] = useState(null);
+
+ // 用于Form的受控数据
+ const [formValues, setFormValues] = useState({
+ name: "",
+ description: "",
+ datasetId: "",
+ templateId: "",
+ });
+
+ const fetchDatasets = async () => {
+ const { data } = await queryDatasetsUsingGet();
+ setDatasets(data.results || []);
+ };
+
+ useEffect(() => {
+ fetchDatasets();
+ }, []);
+
+ const filteredTemplates = mockTemplates.filter(
+ (template) => template.category === selectedCategory
+ );
+
+ const handleDatasetSelect = (datasetId: string) => {
+ const dataset = datasets.find((ds) => ds.id === datasetId) || null;
+ setSelectedDataset(dataset);
+ setFormValues((prev) => ({ ...prev, datasetId }));
+ if (dataset?.type === DatasetType.PRETRAIN_IMAGE) {
+ setSelectedCategory("Computer Vision");
+ } else if (dataset?.type === DatasetType.PRETRAIN_TEXT) {
+ setSelectedCategory("Natural Language Processing");
+ }
+ setSelectedTemplate(null);
+ setFormValues((prev) => ({ ...prev, templateId: "" }));
+ };
+
+ const handleTemplateSelect = (template: Template) => {
+ setSelectedTemplate(template);
+ setFormValues((prev) => ({ ...prev, templateId: template.id }));
+ };
+
+ const handleValuesChange = (_, allValues) => {
+ setFormValues({ ...formValues, ...allValues });
+ };
+
+ const handleSubmit = async () => {
+ try {
+ const values = await form.validateFields();
+ const dataset = datasets.find((ds) => ds.id === values.datasetId);
+ const template = mockTemplates.find(
+ (tpl) => tpl.id === values.templateId
+ );
+ if (!dataset) {
+ message.error("请选择数据集");
+ return;
+ }
+ if (!template) {
+ message.error("请选择标注模板");
+ return;
+ }
+ const taskData = {
+ name: values.name,
+ description: values.description,
+ dataset,
+ template,
+ };
+ // onCreateTask(taskData); // 实际创建逻辑
+ message.success("标注任务创建成功");
+ navigate("/data/annotation");
+ } catch (e) {
+ // 校验失败
+ }
+ };
+
+ const handleSaveCustomTemplate = (templateData: any) => {
+ setSelectedTemplate(templateData);
+ setFormValues((prev) => ({ ...prev, templateId: templateData.id }));
+ message.success(`自定义模板 "${templateData.name}" 已创建`);
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 模板选择 */}
+
+ 模板选择
+
+
+
+ {/* Category Sidebar */}
+
+
+ {templateCategories.map((category) => {
+ const isAvailable =
+ selectedDataset?.type === "image"
+ ? category === "Computer Vision"
+ : category === "Natural Language Processing";
+ return (
+
+ isAvailable && setSelectedCategory(category)
+ }
+ style={{ textAlign: "left", marginBottom: 8 }}
+ >
+ {category}
+
+ );
+ })}
+ }
+ onClick={() => setShowCustomTemplateDialog(true)}
+ >
+ 自定义模板
+
+
+
+ {/* Template Grid */}
+
+
+
+ {filteredTemplates.map((template) => (
+
handleTemplateSelect(template)}
+ >
+ {template.preview && (
+
+

+
+ )}
+
+
+
+ {template.icon}
+
+ {template.name}
+
+
+
+
+ {template.description}
+
+
+
+ ))}
+ {/* Custom Template Option */}
+
setShowCustomTemplateDialog(true)}
+ >
+
+
+
+
+ {selectedTemplate?.isCustom && (
+
+ )}
+
+
+ 创建符合特定需求的标注模板
+
+
+
+
+
+
+
+ {selectedTemplate && (
+
+
+
+ 已选择模板
+
+
+
+ {selectedTemplate.name} - {selectedTemplate.description}
+
+
+ )}
+
+
+
+
+ navigate("/data/annotation")}>取消
+
+ 创建任务
+
+
+
+
+ {/* Custom Template Dialog */}
+
+
+ );
+}
diff --git a/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx b/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx
index 5c80aee5f..8652fb1bf 100644
--- a/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx
+++ b/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx
@@ -1,192 +1,192 @@
-import { queryDatasetsUsingGet } from "@/pages/DataManagement/dataset.api";
-import { mapDataset } from "@/pages/DataManagement/dataset.const";
-import { Button, Form, Input, Modal, Select, message } from "antd";
-import TextArea from "antd/es/input/TextArea";
-import { useEffect, useState } from "react";
-import { createAnnotationTaskUsingPost, queryAnnotationTemplatesUsingGet } from "../../annotation.api";
-import { Dataset } from "@/pages/DataManagement/dataset.model";
-import type { AnnotationTemplate } from "../../annotation.model";
-
-export default function CreateAnnotationTask({
- open,
- onClose,
- onRefresh,
-}: {
- open: boolean;
- onClose: () => void;
- onRefresh: () => void;
-}) {
- const [form] = Form.useForm();
- const [datasets, setDatasets] = useState([]);
- const [templates, setTemplates] = useState([]);
- const [submitting, setSubmitting] = useState(false);
- const [nameManuallyEdited, setNameManuallyEdited] = useState(false);
-
- useEffect(() => {
- if (!open) return;
- const fetchData = async () => {
- try {
- // Fetch datasets
- const { data: datasetData } = await queryDatasetsUsingGet({
- page: 0,
- pageSize: 1000, // Use camelCase for HTTP params
- });
- setDatasets(datasetData.content.map(mapDataset) || []);
-
- // Fetch templates
- const templateResponse = await queryAnnotationTemplatesUsingGet({
- page: 1,
- size: 100, // Backend max is 100 (template API uses 'size' not 'pageSize')
- });
-
- // The API returns: {code, message, data: {content, total, page, ...}}
- if (templateResponse.code === 200 && templateResponse.data) {
- const fetchedTemplates = templateResponse.data.content || [];
- console.log("Fetched templates:", fetchedTemplates);
- setTemplates(fetchedTemplates);
- } else {
- console.error("Failed to fetch templates:", templateResponse);
- setTemplates([]);
- }
- } catch (error) {
- console.error("Error fetching data:", error);
- setTemplates([]);
- }
- };
- fetchData();
- }, [open]);
-
- // Reset form and manual-edit flag when modal opens
- useEffect(() => {
- if (open) {
- form.resetFields();
- setNameManuallyEdited(false);
- }
- }, [open, form]);
-
- const handleSubmit = async () => {
- try {
- const values = await form.validateFields();
- setSubmitting(true);
- // Send templateId instead of labelingConfig
- const requestData = {
- name: values.name,
- description: values.description,
- datasetId: values.datasetId,
- templateId: values.templateId,
- };
- await createAnnotationTaskUsingPost(requestData);
- message?.success?.("创建标注任务成功");
- onClose();
- onRefresh();
- } catch (err: any) {
- console.error("Create annotation task failed", err);
- const msg = err?.message || err?.data?.message || "创建失败,请稍后重试";
- (message as any)?.error?.(msg);
- } finally {
- setSubmitting(false);
- }
- };
-
- return (
-
-
- 取消
-
-
- 确定
-
- >
- }
- width={800}
- >
-
-
-
-
- {/* 标注模板选择 */}
-
-
-
-
- );
-}
+import { queryDatasetsUsingGet } from "@/pages/DataManagement/dataset.api";
+import { mapDataset } from "@/pages/DataManagement/dataset.const";
+import { Button, Form, Input, Modal, Select, message } from "antd";
+import TextArea from "antd/es/input/TextArea";
+import { useEffect, useState } from "react";
+import { createAnnotationTaskUsingPost, queryAnnotationTemplatesUsingGet } from "../../annotation.api";
+import { Dataset } from "@/pages/DataManagement/dataset.model";
+import type { AnnotationTemplate } from "../../annotation.model";
+
+export default function CreateAnnotationTask({
+ open,
+ onClose,
+ onRefresh,
+}: {
+ open: boolean;
+ onClose: () => void;
+ onRefresh: () => void;
+}) {
+ const [form] = Form.useForm();
+ const [datasets, setDatasets] = useState([]);
+ const [templates, setTemplates] = useState([]);
+ const [submitting, setSubmitting] = useState(false);
+ const [nameManuallyEdited, setNameManuallyEdited] = useState(false);
+
+ useEffect(() => {
+ if (!open) return;
+ const fetchData = async () => {
+ try {
+ // Fetch datasets
+ const { data: datasetData } = await queryDatasetsUsingGet({
+ page: 0,
+ pageSize: 1000, // Use camelCase for HTTP params
+ });
+ setDatasets(datasetData.content.map(mapDataset) || []);
+
+ // Fetch templates
+ const templateResponse = await queryAnnotationTemplatesUsingGet({
+ page: 1,
+ size: 100, // Backend max is 100 (template API uses 'size' not 'pageSize')
+ });
+
+ // The API returns: {code, message, data: {content, total, page, ...}}
+ if (templateResponse.code === 200 && templateResponse.data) {
+ const fetchedTemplates = templateResponse.data.content || [];
+ console.log("Fetched templates:", fetchedTemplates);
+ setTemplates(fetchedTemplates);
+ } else {
+ console.error("Failed to fetch templates:", templateResponse);
+ setTemplates([]);
+ }
+ } catch (error) {
+ console.error("Error fetching data:", error);
+ setTemplates([]);
+ }
+ };
+ fetchData();
+ }, [open]);
+
+ // Reset form and manual-edit flag when modal opens
+ useEffect(() => {
+ if (open) {
+ form.resetFields();
+ setNameManuallyEdited(false);
+ }
+ }, [open, form]);
+
+ const handleSubmit = async () => {
+ try {
+ const values = await form.validateFields();
+ setSubmitting(true);
+ // Send templateId instead of labelingConfig
+ const requestData = {
+ name: values.name,
+ description: values.description,
+ datasetId: values.datasetId,
+ templateId: values.templateId,
+ };
+ await createAnnotationTaskUsingPost(requestData);
+ message?.success?.("创建标注任务成功");
+ onClose();
+ onRefresh();
+ } catch (err: any) {
+ console.error("Create annotation task failed", err);
+ const msg = err?.message || err?.data?.message || "创建失败,请稍后重试";
+ (message as any)?.error?.(msg);
+ } finally {
+ setSubmitting(false);
+ }
+ };
+
+ return (
+
+
+ 取消
+
+
+ 确定
+
+ >
+ }
+ width={800}
+ >
+
+
+
+
+ {/* 标注模板选择 */}
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/DataAnnotation/Create/components/CreateAnnptationTaskDialog.tsx b/frontend/src/pages/DataAnnotation/Create/components/CreateAnnptationTaskDialog.tsx
index b7f3e4a49..1531ca19e 100644
--- a/frontend/src/pages/DataAnnotation/Create/components/CreateAnnptationTaskDialog.tsx
+++ b/frontend/src/pages/DataAnnotation/Create/components/CreateAnnptationTaskDialog.tsx
@@ -1,195 +1,195 @@
-import { queryDatasetsUsingGet } from "@/pages/DataManagement/dataset.api";
-import { mapDataset } from "@/pages/DataManagement/dataset.const";
-import { Button, Form, Input, Modal, Select, message } from "antd";
-import TextArea from "antd/es/input/TextArea";
-import { useEffect, useState } from "react";
-import { createAnnotationTaskUsingPost, queryAnnotationTemplatesUsingGet } from "../../annotation.api";
-import { Dataset } from "@/pages/DataManagement/dataset.model";
-import type { AnnotationTemplate } from "../../annotation.model";
-
-export default function CreateAnnotationTask({
- open,
- onClose,
- onRefresh,
-}: {
- open: boolean;
- onClose: () => void;
- onRefresh: () => void;
-}) {
- const [form] = Form.useForm();
- const [datasets, setDatasets] = useState([]);
- const [templates, setTemplates] = useState([]);
- const [submitting, setSubmitting] = useState(false);
- const [nameManuallyEdited, setNameManuallyEdited] = useState(false);
-
- useEffect(() => {
- if (!open) return;
- const fetchData = async () => {
- try {
- // Fetch datasets
- const { data: datasetData } = await queryDatasetsUsingGet({
- page: 0,
- pageSize: 1000, // Use camelCase for HTTP params
- });
- setDatasets(datasetData.content.map(mapDataset) || []);
-
- // Fetch templates
- const templateResponse = await queryAnnotationTemplatesUsingGet({
- page: 1,
- size: 100, // Backend max is 100 (template API uses 'size' not 'pageSize')
- });
-
- // The API returns: {code, message, data: {content, total, page, ...}}
- if (templateResponse.code === 200 && templateResponse.data) {
- const fetchedTemplates = templateResponse.data.content || [];
- console.log("Fetched templates:", fetchedTemplates);
- setTemplates(fetchedTemplates);
- } else {
- console.error("Failed to fetch templates:", templateResponse);
- setTemplates([]);
- }
- } catch (error) {
- console.error("Error fetching data:", error);
- setTemplates([]);
- }
- };
- fetchData();
- }, [open]);
-
- // Reset form and manual-edit flag when modal opens
- useEffect(() => {
- if (open) {
- form.resetFields();
- setNameManuallyEdited(false);
- }
- }, [open, form]);
-
- const handleSubmit = async () => {
- try {
- const values = await form.validateFields();
- setSubmitting(true);
-
- // Send templateId instead of labelingConfig
- const requestData = {
- name: values.name,
- description: values.description,
- datasetId: values.datasetId,
- templateId: values.templateId,
- };
-
- await createAnnotationTaskUsingPost(requestData);
- message?.success?.("创建标注任务成功");
- onClose();
- onRefresh();
- } catch (err: any) {
- console.error("Create annotation task failed", err);
- const msg = err?.message || err?.data?.message || "创建失败,请稍后重试";
- (message as any)?.error?.(msg);
- } finally {
- setSubmitting(false);
- }
- };
-
- return (
-
-
- 取消
-
-
- 确定
-
- >
- }
- width={800}
- >
-
-
-
-
- {/* 标注模板选择 */}
-
-
-
-
- );
-}
+import { queryDatasetsUsingGet } from "@/pages/DataManagement/dataset.api";
+import { mapDataset } from "@/pages/DataManagement/dataset.const";
+import { Button, Form, Input, Modal, Select, message } from "antd";
+import TextArea from "antd/es/input/TextArea";
+import { useEffect, useState } from "react";
+import { createAnnotationTaskUsingPost, queryAnnotationTemplatesUsingGet } from "../../annotation.api";
+import { Dataset } from "@/pages/DataManagement/dataset.model";
+import type { AnnotationTemplate } from "../../annotation.model";
+
+export default function CreateAnnotationTask({
+ open,
+ onClose,
+ onRefresh,
+}: {
+ open: boolean;
+ onClose: () => void;
+ onRefresh: () => void;
+}) {
+ const [form] = Form.useForm();
+ const [datasets, setDatasets] = useState([]);
+ const [templates, setTemplates] = useState([]);
+ const [submitting, setSubmitting] = useState(false);
+ const [nameManuallyEdited, setNameManuallyEdited] = useState(false);
+
+ useEffect(() => {
+ if (!open) return;
+ const fetchData = async () => {
+ try {
+ // Fetch datasets
+ const { data: datasetData } = await queryDatasetsUsingGet({
+ page: 0,
+ pageSize: 1000, // Use camelCase for HTTP params
+ });
+ setDatasets(datasetData.content.map(mapDataset) || []);
+
+ // Fetch templates
+ const templateResponse = await queryAnnotationTemplatesUsingGet({
+ page: 1,
+ size: 100, // Backend max is 100 (template API uses 'size' not 'pageSize')
+ });
+
+ // The API returns: {code, message, data: {content, total, page, ...}}
+ if (templateResponse.code === 200 && templateResponse.data) {
+ const fetchedTemplates = templateResponse.data.content || [];
+ console.log("Fetched templates:", fetchedTemplates);
+ setTemplates(fetchedTemplates);
+ } else {
+ console.error("Failed to fetch templates:", templateResponse);
+ setTemplates([]);
+ }
+ } catch (error) {
+ console.error("Error fetching data:", error);
+ setTemplates([]);
+ }
+ };
+ fetchData();
+ }, [open]);
+
+ // Reset form and manual-edit flag when modal opens
+ useEffect(() => {
+ if (open) {
+ form.resetFields();
+ setNameManuallyEdited(false);
+ }
+ }, [open, form]);
+
+ const handleSubmit = async () => {
+ try {
+ const values = await form.validateFields();
+ setSubmitting(true);
+
+ // Send templateId instead of labelingConfig
+ const requestData = {
+ name: values.name,
+ description: values.description,
+ datasetId: values.datasetId,
+ templateId: values.templateId,
+ };
+
+ await createAnnotationTaskUsingPost(requestData);
+ message?.success?.("创建标注任务成功");
+ onClose();
+ onRefresh();
+ } catch (err: any) {
+ console.error("Create annotation task failed", err);
+ const msg = err?.message || err?.data?.message || "创建失败,请稍后重试";
+ (message as any)?.error?.(msg);
+ } finally {
+ setSubmitting(false);
+ }
+ };
+
+ return (
+
+
+ 取消
+
+
+ 确定
+
+ >
+ }
+ width={800}
+ >
+
+
+
+
+ {/* 标注模板选择 */}
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/DataAnnotation/Create/components/CustomTemplateDialog.tsx b/frontend/src/pages/DataAnnotation/Create/components/CustomTemplateDialog.tsx
index ea8fc913b..30f3cd931 100644
--- a/frontend/src/pages/DataAnnotation/Create/components/CustomTemplateDialog.tsx
+++ b/frontend/src/pages/DataAnnotation/Create/components/CustomTemplateDialog.tsx
@@ -1,225 +1,225 @@
-import { useState } from "react";
-import {
- Modal,
- Input,
- Card,
- message,
- Divider,
- Radio,
- Form,
-} from "antd";
-import {
- AppstoreOutlined,
- BorderOutlined,
- DotChartOutlined,
- EditOutlined,
- CheckSquareOutlined,
- BarsOutlined,
- DeploymentUnitOutlined,
-} from "@ant-design/icons";
-
-interface CustomTemplateDialogProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
- onSaveTemplate: (templateData: any) => void;
- datasetType: "text" | "image";
-}
-
-const { TextArea } = Input;
-
-const defaultImageTemplate = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-const defaultTextTemplate = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-const annotationTools = [
- { id: "rectangle", label: "矩形框", icon: , type: "image" },
- {
- id: "polygon",
- label: "多边形",
- icon: ,
- type: "image",
- },
- { id: "circle", label: "圆形", icon: , type: "image" },
- { id: "point", label: "关键点", icon: , type: "image" },
- { id: "text", label: "文本", icon: , type: "both" },
- { id: "choices", label: "选择题", icon: , type: "both" },
- {
- id: "checkbox",
- label: "多选框",
- icon: ,
- type: "both",
- },
- { id: "textarea", label: "文本域", icon: , type: "both" },
-];
-
-export default function CustomTemplateDialog({
- open,
- onOpenChange,
- onSaveTemplate,
- datasetType,
-}: CustomTemplateDialogProps) {
- const [templateName, setTemplateName] = useState("");
- const [templateDescription, setTemplateDescription] = useState("");
- const [templateCode, setTemplateCode] = useState(
- datasetType === "image" ? defaultImageTemplate : defaultTextTemplate
- );
-
- const handleSave = () => {
- if (!templateName.trim()) {
- message.error("请输入模板名称");
- return;
- }
- if (!templateCode.trim()) {
- message.error("请输入模板代码");
- return;
- }
- const templateData = {
- id: `custom-${Date.now()}`,
- name: templateName,
- description: templateDescription,
- code: templateCode,
- type: datasetType,
- isCustom: true,
- };
- onSaveTemplate(templateData);
- onOpenChange(false);
- message.success("自定义模板已保存");
- setTemplateName("");
- setTemplateDescription("");
- setTemplateCode(
- datasetType === "image" ? defaultImageTemplate : defaultTextTemplate
- );
- };
-
- return (
- onOpenChange(false)}
- okText={"保存模板"}
- onOk={handleSave}
- width={1200}
- className="max-h-[80vh] overflow-auto"
- title="自定义标注模板"
- >
-
-
-
- setTemplateName(e.target.value)}
- />
-
-
- setTemplateDescription(e.target.value)}
- />
-
-
-
-
-
-
预览
-
- }
- >
-
- 病例号:
- undefined
-
-
- 取材部位:
- undefined
-
-
-
-
标注
-
是否有肿瘤
-
- 是[1]
- 否[2]
-
-
-
-
-
-
-
-
-
- );
-}
+import { useState } from "react";
+import {
+ Modal,
+ Input,
+ Card,
+ message,
+ Divider,
+ Radio,
+ Form,
+} from "antd";
+import {
+ AppstoreOutlined,
+ BorderOutlined,
+ DotChartOutlined,
+ EditOutlined,
+ CheckSquareOutlined,
+ BarsOutlined,
+ DeploymentUnitOutlined,
+} from "@ant-design/icons";
+
+interface CustomTemplateDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ onSaveTemplate: (templateData: any) => void;
+ datasetType: "text" | "image";
+}
+
+const { TextArea } = Input;
+
+const defaultImageTemplate = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+const defaultTextTemplate = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+const annotationTools = [
+ { id: "rectangle", label: "矩形框", icon: , type: "image" },
+ {
+ id: "polygon",
+ label: "多边形",
+ icon: ,
+ type: "image",
+ },
+ { id: "circle", label: "圆形", icon: , type: "image" },
+ { id: "point", label: "关键点", icon: , type: "image" },
+ { id: "text", label: "文本", icon: , type: "both" },
+ { id: "choices", label: "选择题", icon: , type: "both" },
+ {
+ id: "checkbox",
+ label: "多选框",
+ icon: ,
+ type: "both",
+ },
+ { id: "textarea", label: "文本域", icon: , type: "both" },
+];
+
+export default function CustomTemplateDialog({
+ open,
+ onOpenChange,
+ onSaveTemplate,
+ datasetType,
+}: CustomTemplateDialogProps) {
+ const [templateName, setTemplateName] = useState("");
+ const [templateDescription, setTemplateDescription] = useState("");
+ const [templateCode, setTemplateCode] = useState(
+ datasetType === "image" ? defaultImageTemplate : defaultTextTemplate
+ );
+
+ const handleSave = () => {
+ if (!templateName.trim()) {
+ message.error("请输入模板名称");
+ return;
+ }
+ if (!templateCode.trim()) {
+ message.error("请输入模板代码");
+ return;
+ }
+ const templateData = {
+ id: `custom-${Date.now()}`,
+ name: templateName,
+ description: templateDescription,
+ code: templateCode,
+ type: datasetType,
+ isCustom: true,
+ };
+ onSaveTemplate(templateData);
+ onOpenChange(false);
+ message.success("自定义模板已保存");
+ setTemplateName("");
+ setTemplateDescription("");
+ setTemplateCode(
+ datasetType === "image" ? defaultImageTemplate : defaultTextTemplate
+ );
+ };
+
+ return (
+ onOpenChange(false)}
+ okText={"保存模板"}
+ onOk={handleSave}
+ width={1200}
+ className="max-h-[80vh] overflow-auto"
+ title="自定义标注模板"
+ >
+
+
+
+ setTemplateName(e.target.value)}
+ />
+
+
+ setTemplateDescription(e.target.value)}
+ />
+
+
+
+
+
+
预览
+
+ }
+ >
+
+ 病例号:
+ undefined
+
+
+ 取材部位:
+ undefined
+
+
+
+
标注
+
是否有肿瘤
+
+ 是[1]
+ 否[2]
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/DataAnnotation/Create/components/LabelingConfigEditor.tsx b/frontend/src/pages/DataAnnotation/Create/components/LabelingConfigEditor.tsx
index d2735698f..8c8d77a3c 100644
--- a/frontend/src/pages/DataAnnotation/Create/components/LabelingConfigEditor.tsx
+++ b/frontend/src/pages/DataAnnotation/Create/components/LabelingConfigEditor.tsx
@@ -1,187 +1,187 @@
-import { Button, Card, Input, InputNumber, Popconfirm, Select, Switch, Tooltip } from "antd";
-import { PlusOutlined, DeleteOutlined } from "@ant-design/icons";
-import { useState, useImperativeHandle, forwardRef } from "react";
-
-type LabelType = "string" | "number" | "enum";
-
-type LabelItem = {
- id: string;
- name: string;
- type: LabelType;
- required?: boolean;
- // for enum: values; for number: min/max
- values?: string[];
- min?: number | null;
- max?: number | null;
- step?: number | null;
-};
-
-type LabelingConfigEditorProps = {
- initial?: any;
- onGenerate: (config: any) => void;
- hideFooter?: boolean;
-};
-
-export default forwardRef(function LabelingConfigEditor(
- { initial, onGenerate, hideFooter }: LabelingConfigEditorProps,
- ref: any
-) {
- const [labels, setLabels] = useState(() => {
- if (initial && initial.labels && Array.isArray(initial.labels)) {
- return initial.labels.map((l: any, idx: number) => ({
- id: `${Date.now()}-${idx}`,
- name: l.name || "",
- type: l.type || "string",
- required: !!l.required,
- values: l.values || (l.type === "enum" ? [] : undefined),
- min: l.min ?? null,
- max: l.max ?? null,
- }));
- }
- return [];
- });
-
- const addLabel = () => {
- setLabels((s) => [
- ...s,
- { id: `${Date.now()}-${Math.random()}`, name: "", type: "string", required: false, step: null },
- ]);
- };
-
- const updateLabel = (id: string, patch: Partial) => {
- setLabels((s) => s.map((l) => (l.id === id ? { ...l, ...patch } : l)));
- };
-
- const removeLabel = (id: string) => {
- setLabels((s) => s.filter((l) => l.id !== id));
- };
-
- const generate = () => {
- // basic validation: label name non-empty
- for (const l of labels) {
- if (!l.name || l.name.trim() === "") {
- // focus not available here, just abort
- // Could show a more friendly UI; keep simple for now
- // eslint-disable-next-line no-alert
- alert("请为所有标签填写名称");
- return;
- }
- if (l.type === "enum") {
- if (!l.values || l.values.length === 0) {
- alert(`枚举标签 ${l.name} 需要至少一个取值`);
- return;
- }
- }
- if (l.type === "number") {
- // validate min/max
- if (l.min != null && l.max != null && l.min > l.max) {
- alert(`数值标签 ${l.name} 的最小值不能大于最大值`);
- return;
- }
- // validate step
- if (l.step != null && (!(typeof l.step === "number") || l.step <= 0)) {
- alert(`数值标签 ${l.name} 的步长必须为大于 0 的数值`);
- return;
- }
- }
- }
-
- const config = {
- labels: labels.map((l) => {
- const item: any = { name: l.name, type: l.type, required: !!l.required };
- if (l.type === "enum") item.values = l.values || [];
- if (l.type === "number") {
- if (l.min != null) item.min = l.min;
- if (l.max != null) item.max = l.max;
- }
- return item;
- }),
- };
-
- onGenerate(config);
- };
-
- useImperativeHandle(ref, () => ({
- addLabel,
- generate,
- }));
-
- return (
-
-
- {labels.map((label) => (
-
-
-
updateLabel(label.id, { name: e.target.value })}
- style={{ flex: "1 1 160px", minWidth: 120 }}
- />
-
-
- {label.type === "string" && 类型:文本}
- {label.type === "number" && 类型:数值,支持 min / max / step}
- {label.type === "enum" && 类型:枚举,每行一个取值(示例:一行写一个值)}
-
-
- ))}
-
- {!hideFooter && (
-
- } onClick={addLabel}>
- 添加标签
-
-
- 生成 JSON 配置
-
-
- )}
-
-
- );
-}
-
-);
+import { Button, Card, Input, InputNumber, Popconfirm, Select, Switch, Tooltip } from "antd";
+import { PlusOutlined, DeleteOutlined } from "@ant-design/icons";
+import { useState, useImperativeHandle, forwardRef } from "react";
+
+type LabelType = "string" | "number" | "enum";
+
+type LabelItem = {
+ id: string;
+ name: string;
+ type: LabelType;
+ required?: boolean;
+ // for enum: values; for number: min/max
+ values?: string[];
+ min?: number | null;
+ max?: number | null;
+ step?: number | null;
+};
+
+type LabelingConfigEditorProps = {
+ initial?: any;
+ onGenerate: (config: any) => void;
+ hideFooter?: boolean;
+};
+
+export default forwardRef(function LabelingConfigEditor(
+ { initial, onGenerate, hideFooter }: LabelingConfigEditorProps,
+ ref: any
+) {
+ const [labels, setLabels] = useState(() => {
+ if (initial && initial.labels && Array.isArray(initial.labels)) {
+ return initial.labels.map((l: any, idx: number) => ({
+ id: `${Date.now()}-${idx}`,
+ name: l.name || "",
+ type: l.type || "string",
+ required: !!l.required,
+ values: l.values || (l.type === "enum" ? [] : undefined),
+ min: l.min ?? null,
+ max: l.max ?? null,
+ }));
+ }
+ return [];
+ });
+
+ const addLabel = () => {
+ setLabels((s) => [
+ ...s,
+ { id: `${Date.now()}-${Math.random()}`, name: "", type: "string", required: false, step: null },
+ ]);
+ };
+
+ const updateLabel = (id: string, patch: Partial) => {
+ setLabels((s) => s.map((l) => (l.id === id ? { ...l, ...patch } : l)));
+ };
+
+ const removeLabel = (id: string) => {
+ setLabels((s) => s.filter((l) => l.id !== id));
+ };
+
+ const generate = () => {
+ // basic validation: label name non-empty
+ for (const l of labels) {
+ if (!l.name || l.name.trim() === "") {
+ // focus not available here, just abort
+ // Could show a more friendly UI; keep simple for now
+ // eslint-disable-next-line no-alert
+ alert("请为所有标签填写名称");
+ return;
+ }
+ if (l.type === "enum") {
+ if (!l.values || l.values.length === 0) {
+ alert(`枚举标签 ${l.name} 需要至少一个取值`);
+ return;
+ }
+ }
+ if (l.type === "number") {
+ // validate min/max
+ if (l.min != null && l.max != null && l.min > l.max) {
+ alert(`数值标签 ${l.name} 的最小值不能大于最大值`);
+ return;
+ }
+ // validate step
+ if (l.step != null && (!(typeof l.step === "number") || l.step <= 0)) {
+ alert(`数值标签 ${l.name} 的步长必须为大于 0 的数值`);
+ return;
+ }
+ }
+ }
+
+ const config = {
+ labels: labels.map((l) => {
+ const item: any = { name: l.name, type: l.type, required: !!l.required };
+ if (l.type === "enum") item.values = l.values || [];
+ if (l.type === "number") {
+ if (l.min != null) item.min = l.min;
+ if (l.max != null) item.max = l.max;
+ }
+ return item;
+ }),
+ };
+
+ onGenerate(config);
+ };
+
+ useImperativeHandle(ref, () => ({
+ addLabel,
+ generate,
+ }));
+
+ return (
+
+
+ {labels.map((label) => (
+
+
+
updateLabel(label.id, { name: e.target.value })}
+ style={{ flex: "1 1 160px", minWidth: 120 }}
+ />
+
+
+ {label.type === "string" && 类型:文本}
+ {label.type === "number" && 类型:数值,支持 min / max / step}
+ {label.type === "enum" && 类型:枚举,每行一个取值(示例:一行写一个值)}
+
+
+ ))}
+
+ {!hideFooter && (
+
+ } onClick={addLabel}>
+ 添加标签
+
+
+ 生成 JSON 配置
+
+
+ )}
+
+
+ );
+}
+
+);
diff --git a/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx b/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx
index 0d8623516..e2e126820 100644
--- a/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx
+++ b/frontend/src/pages/DataAnnotation/Home/DataAnnotation.tsx
@@ -1,398 +1,398 @@
-import { useState, useEffect } from "react";
-import { Card, Button, Table, message, Modal, Tabs } from "antd";
-import {
- PlusOutlined,
- EditOutlined,
- DeleteOutlined,
- SyncOutlined,
-} from "@ant-design/icons";
-import { SearchControls } from "@/components/SearchControls";
-import CardView from "@/components/CardView";
-import type { AnnotationTask } from "../annotation.model";
-import useFetchData from "@/hooks/useFetchData";
-import {
- deleteAnnotationTaskByIdUsingDelete, loginAnnotationUsingGet,
- queryAnnotationTasksUsingGet,
- syncAnnotationTaskUsingPost,
-} from "../annotation.api";
-import { mapAnnotationTask } from "../annotation.const";
-import CreateAnnotationTask from "../Create/components/CreateAnnotationTaskDialog";
-import { ColumnType } from "antd/es/table";
-import { TemplateList } from "../Template";
-// Note: DevelopmentInProgress intentionally not used here
-
-export default function DataAnnotation() {
- // return ;
- const [activeTab, setActiveTab] = useState("tasks");
- const [viewMode, setViewMode] = useState<"list" | "card">("list");
- const [showCreateDialog, setShowCreateDialog] = useState(false);
-
- const {
- loading,
- tableData,
- pagination,
- searchParams,
- fetchData,
- handleFiltersChange,
- handleKeywordChange,
- } = useFetchData(queryAnnotationTasksUsingGet, mapAnnotationTask, 30000, true, [], 0);
-
- const [labelStudioBase, setLabelStudioBase] = useState(null);
- const [selectedRowKeys, setSelectedRowKeys] = useState<(string | number)[]>([]);
- const [selectedRows, setSelectedRows] = useState([]);
-
- // prefetch config on mount so clicking annotate is fast and we know whether base URL exists
- // useEffect ensures this runs once
- useEffect(() => {
- let mounted = true;
- (async () => {
- try {
- const baseUrl = `http://${window.location.hostname}:${parseInt(window.location.port) + 1}`;
- if (mounted) setLabelStudioBase(baseUrl);
- } catch (e) {
- if (mounted) setLabelStudioBase(null);
- }
- })();
- return () => {
- mounted = false;
- };
- }, []);
-
- const handleAnnotate = (task: AnnotationTask) => {
- // Open Label Studio project page in a new tab
- (async () => {
- try {
- // prefer using labeling project id already present on the task
- // `mapAnnotationTask` normalizes upstream fields into `labelingProjId`/`projId`,
- // so prefer those and fall back to the task id if necessary.
- let labelingProjId = (task as any).labelingProjId || (task as any).projId || undefined;
-
- // no fallback external mapping lookup; rely on normalized fields from mapAnnotationTask
-
- // use prefetched base if available
- const base = labelStudioBase;
-
- // no debug logging in production
-
- if (labelingProjId) {
- // only open external Label Studio when we have a configured base url
- await loginAnnotationUsingGet(labelingProjId)
- if (base) {
- const target = `${base}/projects/${labelingProjId}/data`;
- window.open(target, "_blank");
- } else {
- // no external Label Studio URL configured — do not perform internal redirect in this version
- message.error("无法跳转到 Label Studio:未配置 Label Studio 基础 URL");
- return;
- }
- } else {
- // no labeling project id available — do not attempt internal redirect in this version
- message.error("无法跳转到 Label Studio:该映射未绑定标注项目");
- return;
- }
- } catch (error) {
- // on error, surface a user-friendly message instead of redirecting
- message.error("无法跳转到 Label Studio:发生错误,请检查配置或控制台日志");
- return;
- }
- })();
- };
-
- const handleDelete = (task: AnnotationTask) => {
- Modal.confirm({
- title: `确认删除标注任务「${task.name}」吗?`,
- content: (
-
-
删除标注任务不会删除对应数据集。
-
如需保留当前标注结果,请在同步后再删除。
-
- ),
- okText: "删除",
- okType: "danger",
- cancelText: "取消",
- onOk: async () => {
- try {
- await deleteAnnotationTaskByIdUsingDelete(task.id);
- message.success("映射删除成功");
- fetchData();
- // clear selection if deleted item was selected
- setSelectedRowKeys((keys) => keys.filter((k) => k !== task.id));
- setSelectedRows((rows) => rows.filter((r) => r.id !== task.id));
- } catch (e) {
- console.error(e);
- message.error("删除失败,请稍后重试");
- }
- },
- });
- };
-
- const handleSync = (task: AnnotationTask, batchSize: number = 50) => {
- Modal.confirm({
- title: `确认同步标注任务「${task.name}」吗?`,
- content: (
-
-
标注工程中文件列表将与数据集保持一致,差异项将会被修正。
-
标注工程中的标签与数据集中标签将进行合并,冲突项将以最新一次内容为准。
-
- ),
- okText: "同步",
- cancelText: "取消",
- onOk: async () => {
- try {
- await syncAnnotationTaskUsingPost({ id: task.id, batchSize });
- message.success("任务同步请求已发送");
- // optional: refresh list/status
- fetchData();
- // clear selection for the task
- setSelectedRowKeys((keys) => keys.filter((k) => k !== task.id));
- setSelectedRows((rows) => rows.filter((r) => r.id !== task.id));
- } catch (e) {
- console.error(e);
- message.error("同步失败,请稍后重试");
- }
- },
- });
- };
-
- const handleBatchSync = (batchSize: number = 50) => {
- if (!selectedRows || selectedRows.length === 0) return;
- Modal.confirm({
- title: `确认同步所选 ${selectedRows.length} 个标注任务吗?`,
- content: (
-
-
标注工程中文件列表将与数据集保持一致,差异项将会被修正。
-
标注工程中的标签与数据集中标签将进行合并,冲突项将以最新一次内容为准。
-
- ),
- okText: "同步",
- cancelText: "取消",
- onOk: async () => {
- try {
- await Promise.all(
- selectedRows.map((r) => syncAnnotationTaskUsingPost({ id: r.id, batchSize }))
- );
- message.success("批量同步请求已发送");
- fetchData();
- setSelectedRowKeys([]);
- setSelectedRows([]);
- } catch (e) {
- console.error(e);
- message.error("批量同步失败,请稍后重试");
- }
- },
- });
- };
-
- const handleBatchDelete = () => {
- if (!selectedRows || selectedRows.length === 0) return;
- Modal.confirm({
- title: `确认删除所选 ${selectedRows.length} 个标注任务吗?`,
- content: (
-
-
删除标注任务不会删除对应数据集。
-
如需保留当前标注结果,请在同步后再删除。
-
- ),
- okText: "删除",
- okType: "danger",
- cancelText: "取消",
- onOk: async () => {
- try {
- await Promise.all(
- selectedRows.map((r) => deleteAnnotationTaskByIdUsingDelete(r.id))
- );
- message.success("批量删除已完成");
- fetchData();
- setSelectedRowKeys([]);
- setSelectedRows([]);
- } catch (e) {
- console.error(e);
- message.error("批量删除失败,请稍后重试");
- }
- },
- });
- };
-
- const operations = [
- {
- key: "annotate",
- label: "标注",
- icon: (
-
- ),
- onClick: handleAnnotate,
- },
- {
- key: "sync",
- label: "同步",
- icon: ,
- onClick: handleSync,
- },
- {
- key: "delete",
- label: "删除",
- icon: ,
- onClick: handleDelete,
- },
- ];
-
- const columns: ColumnType[] = [
- {
- title: "任务名称",
- dataIndex: "name",
- key: "name",
- fixed: "left" as const,
- },
- {
- title: "任务ID",
- dataIndex: "id",
- key: "id",
- },
- {
- title: "数据集",
- dataIndex: "datasetName",
- key: "datasetName",
- width: 180,
- },
- {
- title: "创建时间",
- dataIndex: "createdAt",
- key: "createdAt",
- width: 180,
- },
- {
- title: "更新时间",
- dataIndex: "updatedAt",
- key: "updatedAt",
- width: 180,
- },
- {
- title: "操作",
- key: "actions",
- fixed: "right" as const,
- width: 150,
- dataIndex: "actions",
- render: (_: any, task: any) => (
-
- {operations.map((operation) => (
- (operation?.onClick as any)?.(task)}
- title={operation.label}
- />
- ))}
-
- ),
- },
- ];
-
- return (
-
- {/* Header */}
-
-
数据标注
-
-
- {/* Tabs */}
-
- {/* Search, Filters and Buttons in one row */}
-
- {/* Left side: Search and view controls */}
-
-
-
-
- {/* Right side: All action buttons */}
-
- handleBatchSync(50)}
- disabled={selectedRowKeys.length === 0}
- >
- 批量同步
-
-
- 批量删除
-
- }
- onClick={() => setShowCreateDialog(true)}
- >
- 创建标注任务
-
-
-
-
- {/* Task List/Card */}
- {viewMode === "list" ? (
-
- {
- setSelectedRowKeys(keys as (string | number)[]);
- setSelectedRows(rows as any[]);
- },
- }}
- scroll={{ x: "max-content", y: "calc(100vh - 24rem)" }}
- />
-
- ) : (
-
- )}
-
- setShowCreateDialog(false)}
- onRefresh={fetchData}
- />
-
- ),
- },
- {
- key: "templates",
- label: "标注模板",
- children: ,
- },
- ]}
- />
-
- );
-}
+import { useState, useEffect } from "react";
+import { Card, Button, Table, message, Modal, Tabs } from "antd";
+import {
+ PlusOutlined,
+ EditOutlined,
+ DeleteOutlined,
+ SyncOutlined,
+} from "@ant-design/icons";
+import { SearchControls } from "@/components/SearchControls";
+import CardView from "@/components/CardView";
+import type { AnnotationTask } from "../annotation.model";
+import useFetchData from "@/hooks/useFetchData";
+import {
+ deleteAnnotationTaskByIdUsingDelete, loginAnnotationUsingGet,
+ queryAnnotationTasksUsingGet,
+ syncAnnotationTaskUsingPost,
+} from "../annotation.api";
+import { mapAnnotationTask } from "../annotation.const";
+import CreateAnnotationTask from "../Create/components/CreateAnnotationTaskDialog";
+import { ColumnType } from "antd/es/table";
+import { TemplateList } from "../Template";
+// Note: DevelopmentInProgress intentionally not used here
+
+export default function DataAnnotation() {
+ // return ;
+ const [activeTab, setActiveTab] = useState("tasks");
+ const [viewMode, setViewMode] = useState<"list" | "card">("list");
+ const [showCreateDialog, setShowCreateDialog] = useState(false);
+
+ const {
+ loading,
+ tableData,
+ pagination,
+ searchParams,
+ fetchData,
+ handleFiltersChange,
+ handleKeywordChange,
+ } = useFetchData(queryAnnotationTasksUsingGet, mapAnnotationTask, 30000, true, [], 0);
+
+ const [labelStudioBase, setLabelStudioBase] = useState(null);
+ const [selectedRowKeys, setSelectedRowKeys] = useState<(string | number)[]>([]);
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ // prefetch config on mount so clicking annotate is fast and we know whether base URL exists
+ // useEffect ensures this runs once
+ useEffect(() => {
+ let mounted = true;
+ (async () => {
+ try {
+ const baseUrl = `http://${window.location.hostname}:${parseInt(window.location.port) + 1}`;
+ if (mounted) setLabelStudioBase(baseUrl);
+ } catch (e) {
+ if (mounted) setLabelStudioBase(null);
+ }
+ })();
+ return () => {
+ mounted = false;
+ };
+ }, []);
+
+ const handleAnnotate = (task: AnnotationTask) => {
+ // Open Label Studio project page in a new tab
+ (async () => {
+ try {
+ // prefer using labeling project id already present on the task
+ // `mapAnnotationTask` normalizes upstream fields into `labelingProjId`/`projId`,
+ // so prefer those and fall back to the task id if necessary.
+ let labelingProjId = (task as any).labelingProjId || (task as any).projId || undefined;
+
+ // no fallback external mapping lookup; rely on normalized fields from mapAnnotationTask
+
+ // use prefetched base if available
+ const base = labelStudioBase;
+
+ // no debug logging in production
+
+ if (labelingProjId) {
+ // only open external Label Studio when we have a configured base url
+ await loginAnnotationUsingGet(labelingProjId)
+ if (base) {
+ const target = `${base}/projects/${labelingProjId}/data`;
+ window.open(target, "_blank");
+ } else {
+ // no external Label Studio URL configured — do not perform internal redirect in this version
+ message.error("无法跳转到 Label Studio:未配置 Label Studio 基础 URL");
+ return;
+ }
+ } else {
+ // no labeling project id available — do not attempt internal redirect in this version
+ message.error("无法跳转到 Label Studio:该映射未绑定标注项目");
+ return;
+ }
+ } catch (error) {
+ // on error, surface a user-friendly message instead of redirecting
+ message.error("无法跳转到 Label Studio:发生错误,请检查配置或控制台日志");
+ return;
+ }
+ })();
+ };
+
+ const handleDelete = (task: AnnotationTask) => {
+ Modal.confirm({
+ title: `确认删除标注任务「${task.name}」吗?`,
+ content: (
+
+
删除标注任务不会删除对应数据集。
+
如需保留当前标注结果,请在同步后再删除。
+
+ ),
+ okText: "删除",
+ okType: "danger",
+ cancelText: "取消",
+ onOk: async () => {
+ try {
+ await deleteAnnotationTaskByIdUsingDelete(task.id);
+ message.success("映射删除成功");
+ fetchData();
+ // clear selection if deleted item was selected
+ setSelectedRowKeys((keys) => keys.filter((k) => k !== task.id));
+ setSelectedRows((rows) => rows.filter((r) => r.id !== task.id));
+ } catch (e) {
+ console.error(e);
+ message.error("删除失败,请稍后重试");
+ }
+ },
+ });
+ };
+
+ const handleSync = (task: AnnotationTask, batchSize: number = 50) => {
+ Modal.confirm({
+ title: `确认同步标注任务「${task.name}」吗?`,
+ content: (
+
+
标注工程中文件列表将与数据集保持一致,差异项将会被修正。
+
标注工程中的标签与数据集中标签将进行合并,冲突项将以最新一次内容为准。
+
+ ),
+ okText: "同步",
+ cancelText: "取消",
+ onOk: async () => {
+ try {
+ await syncAnnotationTaskUsingPost({ id: task.id, batchSize });
+ message.success("任务同步请求已发送");
+ // optional: refresh list/status
+ fetchData();
+ // clear selection for the task
+ setSelectedRowKeys((keys) => keys.filter((k) => k !== task.id));
+ setSelectedRows((rows) => rows.filter((r) => r.id !== task.id));
+ } catch (e) {
+ console.error(e);
+ message.error("同步失败,请稍后重试");
+ }
+ },
+ });
+ };
+
+ const handleBatchSync = (batchSize: number = 50) => {
+ if (!selectedRows || selectedRows.length === 0) return;
+ Modal.confirm({
+ title: `确认同步所选 ${selectedRows.length} 个标注任务吗?`,
+ content: (
+
+
标注工程中文件列表将与数据集保持一致,差异项将会被修正。
+
标注工程中的标签与数据集中标签将进行合并,冲突项将以最新一次内容为准。
+
+ ),
+ okText: "同步",
+ cancelText: "取消",
+ onOk: async () => {
+ try {
+ await Promise.all(
+ selectedRows.map((r) => syncAnnotationTaskUsingPost({ id: r.id, batchSize }))
+ );
+ message.success("批量同步请求已发送");
+ fetchData();
+ setSelectedRowKeys([]);
+ setSelectedRows([]);
+ } catch (e) {
+ console.error(e);
+ message.error("批量同步失败,请稍后重试");
+ }
+ },
+ });
+ };
+
+ const handleBatchDelete = () => {
+ if (!selectedRows || selectedRows.length === 0) return;
+ Modal.confirm({
+ title: `确认删除所选 ${selectedRows.length} 个标注任务吗?`,
+ content: (
+
+
删除标注任务不会删除对应数据集。
+
如需保留当前标注结果,请在同步后再删除。
+
+ ),
+ okText: "删除",
+ okType: "danger",
+ cancelText: "取消",
+ onOk: async () => {
+ try {
+ await Promise.all(
+ selectedRows.map((r) => deleteAnnotationTaskByIdUsingDelete(r.id))
+ );
+ message.success("批量删除已完成");
+ fetchData();
+ setSelectedRowKeys([]);
+ setSelectedRows([]);
+ } catch (e) {
+ console.error(e);
+ message.error("批量删除失败,请稍后重试");
+ }
+ },
+ });
+ };
+
+ const operations = [
+ {
+ key: "annotate",
+ label: "标注",
+ icon: (
+
+ ),
+ onClick: handleAnnotate,
+ },
+ {
+ key: "sync",
+ label: "同步",
+ icon: ,
+ onClick: handleSync,
+ },
+ {
+ key: "delete",
+ label: "删除",
+ icon: ,
+ onClick: handleDelete,
+ },
+ ];
+
+ const columns: ColumnType[] = [
+ {
+ title: "任务名称",
+ dataIndex: "name",
+ key: "name",
+ fixed: "left" as const,
+ },
+ {
+ title: "任务ID",
+ dataIndex: "id",
+ key: "id",
+ },
+ {
+ title: "数据集",
+ dataIndex: "datasetName",
+ key: "datasetName",
+ width: 180,
+ },
+ {
+ title: "创建时间",
+ dataIndex: "createdAt",
+ key: "createdAt",
+ width: 180,
+ },
+ {
+ title: "更新时间",
+ dataIndex: "updatedAt",
+ key: "updatedAt",
+ width: 180,
+ },
+ {
+ title: "操作",
+ key: "actions",
+ fixed: "right" as const,
+ width: 150,
+ dataIndex: "actions",
+ render: (_: any, task: any) => (
+
+ {operations.map((operation) => (
+ (operation?.onClick as any)?.(task)}
+ title={operation.label}
+ />
+ ))}
+
+ ),
+ },
+ ];
+
+ return (
+
+ {/* Header */}
+
+
数据标注
+
+
+ {/* Tabs */}
+
+ {/* Search, Filters and Buttons in one row */}
+
+ {/* Left side: Search and view controls */}
+
+
+
+
+ {/* Right side: All action buttons */}
+
+ handleBatchSync(50)}
+ disabled={selectedRowKeys.length === 0}
+ >
+ 批量同步
+
+
+ 批量删除
+
+ }
+ onClick={() => setShowCreateDialog(true)}
+ >
+ 创建标注任务
+
+
+
+
+ {/* Task List/Card */}
+ {viewMode === "list" ? (
+
+ {
+ setSelectedRowKeys(keys as (string | number)[]);
+ setSelectedRows(rows as any[]);
+ },
+ }}
+ scroll={{ x: "max-content", y: "calc(100vh - 24rem)" }}
+ />
+
+ ) : (
+
+ )}
+
+ setShowCreateDialog(false)}
+ onRefresh={fetchData}
+ />
+
+ ),
+ },
+ {
+ key: "templates",
+ label: "标注模板",
+ children: ,
+ },
+ ]}
+ />
+
+ );
+}
diff --git a/frontend/src/pages/DataAnnotation/Template/TemplateDetail.tsx b/frontend/src/pages/DataAnnotation/Template/TemplateDetail.tsx
index 47e51f888..202aa6f1a 100644
--- a/frontend/src/pages/DataAnnotation/Template/TemplateDetail.tsx
+++ b/frontend/src/pages/DataAnnotation/Template/TemplateDetail.tsx
@@ -1,155 +1,155 @@
-import React from "react";
-import { Modal, Descriptions, Tag, Space, Divider, Card, Typography } from "antd";
-import type { AnnotationTemplate } from "../annotation.model";
-
-const { Text, Paragraph } = Typography;
-
-interface TemplateDetailProps {
- visible: boolean;
- template?: AnnotationTemplate;
- onClose: () => void;
-}
-
-const TemplateDetail: React.FC = ({
- visible,
- template,
- onClose,
-}) => {
- if (!template) return null;
-
- return (
-
-
-
- {template.name}
-
-
- {template.description || "-"}
-
-
- {template.dataType}
-
-
- {template.labelingType}
-
-
- {template.category}
-
-
- {template.style}
-
-
-
- {template.builtIn ? "系统内置" : "自定义"}
-
-
-
- {template.version}
-
-
- {new Date(template.createdAt).toLocaleString()}
-
- {template.updatedAt && (
-
- {new Date(template.updatedAt).toLocaleString()}
-
- )}
-
-
- 配置详情
-
-
-
- {template.configuration.objects.map((obj, index) => (
-
-
- 名称:
- {obj.name}
- 类型:
- {obj.type}
- 值:
- {obj.value}
-
-
- ))}
-
-
-
-
-
- {template.configuration.labels.map((label, index) => (
-
-
-
- 来源名称:
- {label.fromName}
-
- 目标名称:
- {label.toName}
-
- 类型:
- {label.type}
-
- {label.required && 必填}
-
-
- {label.description && (
-
- 描述:
- {label.description}
-
- )}
-
- {label.options && label.options.length > 0 && (
-
-
选项:
-
- {label.options.map((opt, i) => (
- {opt}
- ))}
-
-
- )}
-
- {label.labels && label.labels.length > 0 && (
-
-
标签:
-
- {label.labels.map((lbl, i) => (
- {lbl}
- ))}
-
-
- )}
-
-
- ))}
-
-
-
- {template.labelConfig && (
-
-
-
- {template.labelConfig}
-
-
-
- )}
-
- );
-};
-
-export default TemplateDetail;
+import React from "react";
+import { Modal, Descriptions, Tag, Space, Divider, Card, Typography } from "antd";
+import type { AnnotationTemplate } from "../annotation.model";
+
+const { Text, Paragraph } = Typography;
+
+interface TemplateDetailProps {
+ visible: boolean;
+ template?: AnnotationTemplate;
+ onClose: () => void;
+}
+
+const TemplateDetail: React.FC = ({
+ visible,
+ template,
+ onClose,
+}) => {
+ if (!template) return null;
+
+ return (
+
+
+
+ {template.name}
+
+
+ {template.description || "-"}
+
+
+ {template.dataType}
+
+
+ {template.labelingType}
+
+
+ {template.category}
+
+
+ {template.style}
+
+
+
+ {template.builtIn ? "系统内置" : "自定义"}
+
+
+
+ {template.version}
+
+
+ {new Date(template.createdAt).toLocaleString()}
+
+ {template.updatedAt && (
+
+ {new Date(template.updatedAt).toLocaleString()}
+
+ )}
+
+
+ 配置详情
+
+
+
+ {template.configuration.objects.map((obj, index) => (
+
+
+ 名称:
+ {obj.name}
+ 类型:
+ {obj.type}
+ 值:
+ {obj.value}
+
+
+ ))}
+
+
+
+
+
+ {template.configuration.labels.map((label, index) => (
+
+
+
+ 来源名称:
+ {label.fromName}
+
+ 目标名称:
+ {label.toName}
+
+ 类型:
+ {label.type}
+
+ {label.required && 必填}
+
+
+ {label.description && (
+
+ 描述:
+ {label.description}
+
+ )}
+
+ {label.options && label.options.length > 0 && (
+
+
选项:
+
+ {label.options.map((opt, i) => (
+ {opt}
+ ))}
+
+
+ )}
+
+ {label.labels && label.labels.length > 0 && (
+
+
标签:
+
+ {label.labels.map((lbl, i) => (
+ {lbl}
+ ))}
+
+
+ )}
+
+
+ ))}
+
+
+
+ {template.labelConfig && (
+
+
+
+ {template.labelConfig}
+
+
+
+ )}
+
+ );
+};
+
+export default TemplateDetail;
diff --git a/frontend/src/pages/DataAnnotation/Template/TemplateForm.tsx b/frontend/src/pages/DataAnnotation/Template/TemplateForm.tsx
index 81a485847..511c831fe 100644
--- a/frontend/src/pages/DataAnnotation/Template/TemplateForm.tsx
+++ b/frontend/src/pages/DataAnnotation/Template/TemplateForm.tsx
@@ -1,427 +1,427 @@
-import React, { useState, useEffect } from "react";
-import {
- Modal,
- Form,
- Input,
- Select,
- Button,
- Space,
- message,
- Divider,
- Card,
- Checkbox,
-} from "antd";
-import { PlusOutlined, MinusCircleOutlined } from "@ant-design/icons";
-import {
- createAnnotationTemplateUsingPost,
- updateAnnotationTemplateByIdUsingPut,
-} from "../annotation.api";
-import type { AnnotationTemplate } from "../annotation.model";
-import TagSelector from "./components/TagSelector";
-
-const { TextArea } = Input;
-const { Option } = Select;
-
-interface TemplateFormProps {
- visible: boolean;
- mode: "create" | "edit";
- template?: AnnotationTemplate;
- onSuccess: () => void;
- onCancel: () => void;
-}
-
-const TemplateForm: React.FC = ({
- visible,
- mode,
- template,
- onSuccess,
- onCancel,
-}) => {
- const [form] = Form.useForm();
- const [loading, setLoading] = useState(false);
-
- useEffect(() => {
- if (visible && template && mode === "edit") {
- form.setFieldsValue({
- name: template.name,
- description: template.description,
- dataType: template.dataType,
- labelingType: template.labelingType,
- style: template.style,
- category: template.category,
- labels: template.configuration.labels,
- objects: template.configuration.objects,
- });
- } else if (visible && mode === "create") {
- form.resetFields();
- // Set default values
- form.setFieldsValue({
- style: "horizontal",
- category: "custom",
- labels: [],
- objects: [{ name: "image", type: "Image", value: "$image" }],
- });
- }
- }, [visible, template, mode, form]);
-
- const handleSubmit = async () => {
- try {
- const values = await form.validateFields();
- setLoading(true);
-
- console.log("Form values:", values);
-
- const requestData = {
- name: values.name,
- description: values.description,
- dataType: values.dataType,
- labelingType: values.labelingType,
- style: values.style,
- category: values.category,
- configuration: {
- labels: values.labels,
- objects: values.objects,
- },
- };
-
- console.log("Request data:", requestData);
-
- let response;
- if (mode === "create") {
- response = await createAnnotationTemplateUsingPost(requestData);
- } else {
- response = await updateAnnotationTemplateByIdUsingPut(template!.id, requestData);
- }
-
- if (response.code === 200) {
- message.success(`模板${mode === "create" ? "创建" : "更新"}成功`);
- form.resetFields();
- onSuccess();
- } else {
- message.error(response.message || `模板${mode === "create" ? "创建" : "更新"}失败`);
- }
- } catch (error: any) {
- if (error.errorFields) {
- message.error("请填写所有必填字段");
- } else {
- message.error(`模板${mode === "create" ? "创建" : "更新"}失败`);
- console.error(error);
- }
- } finally {
- setLoading(false);
- }
- };
-
- const needsOptions = (type: string) => {
- return ["Choices", "RectangleLabels", "PolygonLabels", "Labels"].includes(type);
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 数据对象
-
-
- {(fields, { add, remove }) => (
- <>
- {fields.map((field) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {fields.length > 1 && (
- remove(field.name)}
- />
- )}
-
-
- ))}
- add()} block icon={}>
- 添加对象
-
- >
- )}
-
-
- 标签控件
-
-
- {(fields, { add, remove }) => (
- <>
- {fields.map((field) => (
-
- 控件 {fields.indexOf(field) + 1}
-
- {() => {
- const controlType = form.getFieldValue(["labels", field.name, "type"]);
- const fromName = form.getFieldValue(["labels", field.name, "fromName"]);
- if (controlType || fromName) {
- return (
-
- ({fromName || '未命名'} - {controlType || '未设置类型'})
-
- );
- }
- return null;
- }}
-
-
- }
- extra={
- remove(field.name)}
- />
- }
- >
-
- {/* Row 1: 控件名称, 标注目标对象, 控件类型 */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 必填
-
-
-
- {/* Row 2: 取值范围定义(添加选项) - Conditionally rendered based on type */}
- {
- const prevType = prevValues.labels?.[field.name]?.type;
- const currType = currentValues.labels?.[field.name]?.type;
- return prevType !== currType;
- }}
- >
- {({ getFieldValue }) => {
- const controlType = getFieldValue(["labels", field.name, "type"]);
- const fieldName = controlType === "Choices" ? "options" : "labels";
-
- if (needsOptions(controlType)) {
- return (
-
-
-
- );
- }
- return null;
- }}
-
-
- {/* Row 3: 描述 */}
-
-
-
-
-
- ))}
-
- add({
- fromName: "",
- toName: "",
- type: "Choices",
- required: false,
- })
- }
- block
- icon={}
- >
- 添加标签控件
-
- >
- )}
-
-
-
- );
-};
-
-export default TemplateForm;
+import React, { useState, useEffect } from "react";
+import {
+ Modal,
+ Form,
+ Input,
+ Select,
+ Button,
+ Space,
+ message,
+ Divider,
+ Card,
+ Checkbox,
+} from "antd";
+import { PlusOutlined, MinusCircleOutlined } from "@ant-design/icons";
+import {
+ createAnnotationTemplateUsingPost,
+ updateAnnotationTemplateByIdUsingPut,
+} from "../annotation.api";
+import type { AnnotationTemplate } from "../annotation.model";
+import TagSelector from "./components/TagSelector";
+
+const { TextArea } = Input;
+const { Option } = Select;
+
+interface TemplateFormProps {
+ visible: boolean;
+ mode: "create" | "edit";
+ template?: AnnotationTemplate;
+ onSuccess: () => void;
+ onCancel: () => void;
+}
+
+const TemplateForm: React.FC = ({
+ visible,
+ mode,
+ template,
+ onSuccess,
+ onCancel,
+}) => {
+ const [form] = Form.useForm();
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ if (visible && template && mode === "edit") {
+ form.setFieldsValue({
+ name: template.name,
+ description: template.description,
+ dataType: template.dataType,
+ labelingType: template.labelingType,
+ style: template.style,
+ category: template.category,
+ labels: template.configuration.labels,
+ objects: template.configuration.objects,
+ });
+ } else if (visible && mode === "create") {
+ form.resetFields();
+ // Set default values
+ form.setFieldsValue({
+ style: "horizontal",
+ category: "custom",
+ labels: [],
+ objects: [{ name: "image", type: "Image", value: "$image" }],
+ });
+ }
+ }, [visible, template, mode, form]);
+
+ const handleSubmit = async () => {
+ try {
+ const values = await form.validateFields();
+ setLoading(true);
+
+ console.log("Form values:", values);
+
+ const requestData = {
+ name: values.name,
+ description: values.description,
+ dataType: values.dataType,
+ labelingType: values.labelingType,
+ style: values.style,
+ category: values.category,
+ configuration: {
+ labels: values.labels,
+ objects: values.objects,
+ },
+ };
+
+ console.log("Request data:", requestData);
+
+ let response;
+ if (mode === "create") {
+ response = await createAnnotationTemplateUsingPost(requestData);
+ } else {
+ response = await updateAnnotationTemplateByIdUsingPut(template!.id, requestData);
+ }
+
+ if (response.code === 200) {
+ message.success(`模板${mode === "create" ? "创建" : "更新"}成功`);
+ form.resetFields();
+ onSuccess();
+ } else {
+ message.error(response.message || `模板${mode === "create" ? "创建" : "更新"}失败`);
+ }
+ } catch (error: any) {
+ if (error.errorFields) {
+ message.error("请填写所有必填字段");
+ } else {
+ message.error(`模板${mode === "create" ? "创建" : "更新"}失败`);
+ console.error(error);
+ }
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const needsOptions = (type: string) => {
+ return ["Choices", "RectangleLabels", "PolygonLabels", "Labels"].includes(type);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 数据对象
+
+
+ {(fields, { add, remove }) => (
+ <>
+ {fields.map((field) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {fields.length > 1 && (
+ remove(field.name)}
+ />
+ )}
+
+
+ ))}
+ add()} block icon={}>
+ 添加对象
+
+ >
+ )}
+
+
+ 标签控件
+
+
+ {(fields, { add, remove }) => (
+ <>
+ {fields.map((field) => (
+
+ 控件 {fields.indexOf(field) + 1}
+
+ {() => {
+ const controlType = form.getFieldValue(["labels", field.name, "type"]);
+ const fromName = form.getFieldValue(["labels", field.name, "fromName"]);
+ if (controlType || fromName) {
+ return (
+
+ ({fromName || '未命名'} - {controlType || '未设置类型'})
+
+ );
+ }
+ return null;
+ }}
+
+
+ }
+ extra={
+ remove(field.name)}
+ />
+ }
+ >
+
+ {/* Row 1: 控件名称, 标注目标对象, 控件类型 */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 必填
+
+
+
+ {/* Row 2: 取值范围定义(添加选项) - Conditionally rendered based on type */}
+ {
+ const prevType = prevValues.labels?.[field.name]?.type;
+ const currType = currentValues.labels?.[field.name]?.type;
+ return prevType !== currType;
+ }}
+ >
+ {({ getFieldValue }) => {
+ const controlType = getFieldValue(["labels", field.name, "type"]);
+ const fieldName = controlType === "Choices" ? "options" : "labels";
+
+ if (needsOptions(controlType)) {
+ return (
+
+
+
+ );
+ }
+ return null;
+ }}
+
+
+ {/* Row 3: 描述 */}
+
+
+
+
+
+ ))}
+
+ add({
+ fromName: "",
+ toName: "",
+ type: "Choices",
+ required: false,
+ })
+ }
+ block
+ icon={}
+ >
+ 添加标签控件
+
+ >
+ )}
+
+
+
+ );
+};
+
+export default TemplateForm;
diff --git a/frontend/src/pages/DataAnnotation/Template/TemplateList.tsx b/frontend/src/pages/DataAnnotation/Template/TemplateList.tsx
index 40e225c6b..2a4370bb7 100644
--- a/frontend/src/pages/DataAnnotation/Template/TemplateList.tsx
+++ b/frontend/src/pages/DataAnnotation/Template/TemplateList.tsx
@@ -1,311 +1,311 @@
-import React, { useState } from "react";
-import {
- Button,
- Table,
- Space,
- Tag,
- message,
- Tooltip,
- Popconfirm,
- Card,
-} from "antd";
-import {
- PlusOutlined,
- EditOutlined,
- DeleteOutlined,
- EyeOutlined,
-} from "@ant-design/icons";
-import type { ColumnsType } from "antd/es/table";
-import {
- queryAnnotationTemplatesUsingGet,
- deleteAnnotationTemplateByIdUsingDelete,
-} from "../annotation.api";
-import type { AnnotationTemplate } from "../annotation.model";
-import TemplateForm from "./TemplateForm.tsx";
-import TemplateDetail from "./TemplateDetail.tsx";
-import {SearchControls} from "@/components/SearchControls.tsx";
-import useFetchData from "@/hooks/useFetchData.ts";
-import {
- AnnotationTypeMap,
- ClassificationMap,
- DataTypeMap,
- TemplateTypeMap
-} from "@/pages/DataAnnotation/annotation.const.tsx";
-
-const TemplateList: React.FC = () => {
- const filterOptions = [
- {
- key: "category",
- label: "分类",
- options: [...Object.values(ClassificationMap)],
- },
- {
- key: "dataType",
- label: "数据类型",
- options: [...Object.values(DataTypeMap)],
- },
- {
- key: "labelingType",
- label: "标注类型",
- options: [...Object.values(AnnotationTypeMap)],
- },
- {
- key: "builtIn",
- label: "模板类型",
- options: [...Object.values(TemplateTypeMap)],
- },
- ];
-
- // Modals
- const [isFormVisible, setIsFormVisible] = useState(false);
- const [isDetailVisible, setIsDetailVisible] = useState(false);
- const [selectedTemplate, setSelectedTemplate] = useState();
- const [formMode, setFormMode] = useState<"create" | "edit">("create");
-
- const {
- loading,
- tableData,
- pagination,
- searchParams,
- setSearchParams,
- fetchData,
- handleFiltersChange,
- handleKeywordChange,
- } = useFetchData(queryAnnotationTemplatesUsingGet, undefined, undefined, undefined, undefined, 0);
-
- const handleCreate = () => {
- setFormMode("create");
- setSelectedTemplate(undefined);
- setIsFormVisible(true);
- };
-
- const handleEdit = (template: AnnotationTemplate) => {
- setFormMode("edit");
- setSelectedTemplate(template);
- setIsFormVisible(true);
- };
-
- const handleView = (template: AnnotationTemplate) => {
- setSelectedTemplate(template);
- setIsDetailVisible(true);
- };
-
- const handleDelete = async (templateId: string) => {
- try {
- const response = await deleteAnnotationTemplateByIdUsingDelete(templateId);
- if (response.code === 200) {
- message.success("模板删除成功");
- fetchData();
- } else {
- message.error(response.message || "删除模板失败");
- }
- } catch (error) {
- message.error("删除模板失败");
- console.error(error);
- }
- };
-
- const handleFormSuccess = () => {
- setIsFormVisible(false);
- fetchData();
- };
-
- const getCategoryColor = (category: string) => {
- const colors: Record = {
- "computer-vision": "blue",
- "nlp": "green",
- "audio": "purple",
- "quality-control": "orange",
- "custom": "default",
- };
- return colors[category] || "default";
- };
-
- const columns: ColumnsType = [
- {
- title: "模板名称",
- dataIndex: "name",
- key: "name",
- width: 200,
- ellipsis: true,
- onFilter: (value, record) =>
- record.name.toLowerCase().includes(value.toString().toLowerCase()) ||
- (record.description?.toLowerCase().includes(value.toString().toLowerCase()) ?? false),
- },
- {
- title: "描述",
- dataIndex: "description",
- key: "description",
- ellipsis: {
- showTitle: false,
- },
- render: (description: string) => (
-
-
- {description}
-
-
- ),
- },
- {
- title: "数据类型",
- dataIndex: "dataType",
- key: "dataType",
- width: 120,
- render: (dataType: string) => (
- {dataType}
- ),
- },
- {
- title: "标注类型",
- dataIndex: "labelingType",
- key: "labelingType",
- width: 150,
- render: (labelingType: string) => (
- {labelingType}
- ),
- },
- {
- title: "分类",
- dataIndex: "category",
- key: "category",
- width: 150,
- render: (category: string) => (
- {category}
- ),
- },
- {
- title: "类型",
- dataIndex: "builtIn",
- key: "builtIn",
- width: 100,
- render: (builtIn: boolean) => (
-
- {builtIn ? "系统内置" : "自定义"}
-
- ),
- },
- {
- title: "版本",
- dataIndex: "version",
- key: "version",
- width: 80,
- },
- {
- title: "创建时间",
- dataIndex: "createdAt",
- key: "createdAt",
- width: 180,
- render: (date: string) => new Date(date).toLocaleString(),
- },
- {
- title: "操作",
- key: "action",
- width: 200,
- fixed: "right",
- render: (_, record) => (
-
-
- }
- onClick={() => handleView(record)}
- />
-
- {!record.builtIn && (
- <>
-
- }
- onClick={() => handleEdit(record)}
- />
-
- handleDelete(record.id)}
- okText="确定"
- cancelText="取消"
- >
-
- }
- />
-
-
- >
- )}
-
- ),
- },
- ];
-
- return (
-
- {/* Search, Filters and Buttons in one row */}
-
- {/* Left side: Search and Filters */}
-
- setSearchParams({ ...searchParams, filter: {} })}
- />
-
-
- {/* Right side: Create button */}
-
- } onClick={handleCreate}>
- 创建模板
-
-
-
-
-
-
-
-
-
setIsFormVisible(false)}
- />
-
- setIsDetailVisible(false)}
- />
-
- );
-};
-
-export default TemplateList;
-export { TemplateList };
+import React, { useState } from "react";
+import {
+ Button,
+ Table,
+ Space,
+ Tag,
+ message,
+ Tooltip,
+ Popconfirm,
+ Card,
+} from "antd";
+import {
+ PlusOutlined,
+ EditOutlined,
+ DeleteOutlined,
+ EyeOutlined,
+} from "@ant-design/icons";
+import type { ColumnsType } from "antd/es/table";
+import {
+ queryAnnotationTemplatesUsingGet,
+ deleteAnnotationTemplateByIdUsingDelete,
+} from "../annotation.api";
+import type { AnnotationTemplate } from "../annotation.model";
+import TemplateForm from "./TemplateForm.tsx";
+import TemplateDetail from "./TemplateDetail.tsx";
+import {SearchControls} from "@/components/SearchControls.tsx";
+import useFetchData from "@/hooks/useFetchData.ts";
+import {
+ AnnotationTypeMap,
+ ClassificationMap,
+ DataTypeMap,
+ TemplateTypeMap
+} from "@/pages/DataAnnotation/annotation.const.tsx";
+
+const TemplateList: React.FC = () => {
+ const filterOptions = [
+ {
+ key: "category",
+ label: "分类",
+ options: [...Object.values(ClassificationMap)],
+ },
+ {
+ key: "dataType",
+ label: "数据类型",
+ options: [...Object.values(DataTypeMap)],
+ },
+ {
+ key: "labelingType",
+ label: "标注类型",
+ options: [...Object.values(AnnotationTypeMap)],
+ },
+ {
+ key: "builtIn",
+ label: "模板类型",
+ options: [...Object.values(TemplateTypeMap)],
+ },
+ ];
+
+ // Modals
+ const [isFormVisible, setIsFormVisible] = useState(false);
+ const [isDetailVisible, setIsDetailVisible] = useState(false);
+ const [selectedTemplate, setSelectedTemplate] = useState();
+ const [formMode, setFormMode] = useState<"create" | "edit">("create");
+
+ const {
+ loading,
+ tableData,
+ pagination,
+ searchParams,
+ setSearchParams,
+ fetchData,
+ handleFiltersChange,
+ handleKeywordChange,
+ } = useFetchData(queryAnnotationTemplatesUsingGet, undefined, undefined, undefined, undefined, 0);
+
+ const handleCreate = () => {
+ setFormMode("create");
+ setSelectedTemplate(undefined);
+ setIsFormVisible(true);
+ };
+
+ const handleEdit = (template: AnnotationTemplate) => {
+ setFormMode("edit");
+ setSelectedTemplate(template);
+ setIsFormVisible(true);
+ };
+
+ const handleView = (template: AnnotationTemplate) => {
+ setSelectedTemplate(template);
+ setIsDetailVisible(true);
+ };
+
+ const handleDelete = async (templateId: string) => {
+ try {
+ const response = await deleteAnnotationTemplateByIdUsingDelete(templateId);
+ if (response.code === 200) {
+ message.success("模板删除成功");
+ fetchData();
+ } else {
+ message.error(response.message || "删除模板失败");
+ }
+ } catch (error) {
+ message.error("删除模板失败");
+ console.error(error);
+ }
+ };
+
+ const handleFormSuccess = () => {
+ setIsFormVisible(false);
+ fetchData();
+ };
+
+ const getCategoryColor = (category: string) => {
+ const colors: Record = {
+ "computer-vision": "blue",
+ "nlp": "green",
+ "audio": "purple",
+ "quality-control": "orange",
+ "custom": "default",
+ };
+ return colors[category] || "default";
+ };
+
+ const columns: ColumnsType = [
+ {
+ title: "模板名称",
+ dataIndex: "name",
+ key: "name",
+ width: 200,
+ ellipsis: true,
+ onFilter: (value, record) =>
+ record.name.toLowerCase().includes(value.toString().toLowerCase()) ||
+ (record.description?.toLowerCase().includes(value.toString().toLowerCase()) ?? false),
+ },
+ {
+ title: "描述",
+ dataIndex: "description",
+ key: "description",
+ ellipsis: {
+ showTitle: false,
+ },
+ render: (description: string) => (
+
+
+ {description}
+
+
+ ),
+ },
+ {
+ title: "数据类型",
+ dataIndex: "dataType",
+ key: "dataType",
+ width: 120,
+ render: (dataType: string) => (
+ {dataType}
+ ),
+ },
+ {
+ title: "标注类型",
+ dataIndex: "labelingType",
+ key: "labelingType",
+ width: 150,
+ render: (labelingType: string) => (
+ {labelingType}
+ ),
+ },
+ {
+ title: "分类",
+ dataIndex: "category",
+ key: "category",
+ width: 150,
+ render: (category: string) => (
+ {category}
+ ),
+ },
+ {
+ title: "类型",
+ dataIndex: "builtIn",
+ key: "builtIn",
+ width: 100,
+ render: (builtIn: boolean) => (
+
+ {builtIn ? "系统内置" : "自定义"}
+
+ ),
+ },
+ {
+ title: "版本",
+ dataIndex: "version",
+ key: "version",
+ width: 80,
+ },
+ {
+ title: "创建时间",
+ dataIndex: "createdAt",
+ key: "createdAt",
+ width: 180,
+ render: (date: string) => new Date(date).toLocaleString(),
+ },
+ {
+ title: "操作",
+ key: "action",
+ width: 200,
+ fixed: "right",
+ render: (_, record) => (
+
+
+ }
+ onClick={() => handleView(record)}
+ />
+
+ {!record.builtIn && (
+ <>
+
+ }
+ onClick={() => handleEdit(record)}
+ />
+
+ handleDelete(record.id)}
+ okText="确定"
+ cancelText="取消"
+ >
+
+ }
+ />
+
+
+ >
+ )}
+
+ ),
+ },
+ ];
+
+ return (
+
+ {/* Search, Filters and Buttons in one row */}
+
+ {/* Left side: Search and Filters */}
+
+ setSearchParams({ ...searchParams, filter: {} })}
+ />
+
+
+ {/* Right side: Create button */}
+
+ } onClick={handleCreate}>
+ 创建模板
+
+
+
+
+
+
+
+
+
setIsFormVisible(false)}
+ />
+
+ setIsDetailVisible(false)}
+ />
+
+ );
+};
+
+export default TemplateList;
+export { TemplateList };
diff --git a/frontend/src/pages/DataAnnotation/Template/VisualTemplateBuilder.tsx b/frontend/src/pages/DataAnnotation/Template/VisualTemplateBuilder.tsx
index 9f7dacda0..88e4d8a1d 100644
--- a/frontend/src/pages/DataAnnotation/Template/VisualTemplateBuilder.tsx
+++ b/frontend/src/pages/DataAnnotation/Template/VisualTemplateBuilder.tsx
@@ -1,161 +1,161 @@
-import React, { useState } from "react";
-import {
- Card,
- Button,
- Space,
- Row,
- Col,
- Drawer,
- Typography,
- message,
-} from "antd";
-import {
- PlusOutlined,
- EyeOutlined,
- CodeOutlined,
- AppstoreOutlined,
-} from "@ant-design/icons";
-import { TagBrowser } from "./components";
-
-const { Paragraph } = Typography;
-
-interface VisualTemplateBuilderProps {
- onSave?: (templateCode: string) => void;
-}
-
-/**
- * Visual Template Builder
- * Provides a drag-and-drop interface for building Label Studio templates
- */
-const VisualTemplateBuilder: React.FC = ({
- onSave,
-}) => {
- const [drawerVisible, setDrawerVisible] = useState(false);
- const [previewVisible, setPreviewVisible] = useState(false);
- const [selectedTags, setSelectedTags] = useState<
- Array<{ name: string; category: "object" | "control" }>
- >([]);
-
- const handleTagSelect = (tagName: string, category: "object" | "control") => {
- message.info(`选择了 ${category === "object" ? "对象" : "控件"}: ${tagName}`);
- setSelectedTags([...selectedTags, { name: tagName, category }]);
- setDrawerVisible(false);
- };
-
- const handleSave = () => {
- // TODO: Generate template XML from selectedTags
- message.success("模板保存成功");
- onSave?.("");
- };
-
- return (
-
-
-
-
- }
- onClick={() => setDrawerVisible(true)}
- >
- 浏览标签
-
- }
- onClick={() => setPreviewVisible(true)}
- >
- 查看代码
-
- }
- onClick={handleSave}
- >
- 保存模板
-
-
- }
- >
-
- {selectedTags.length === 0 ? (
-
-
- 点击"浏览标签"开始构建您的标注模板
-
-
}
- onClick={() => setDrawerVisible(true)}
- >
- 添加标签
-
-
- ) : (
-
- {selectedTags.map((tag, index) => (
-
-
- {tag.category === "object" ? "对象" : "控件"}: {tag.name}
-
-
- ))}
-
- )}
-
-
-
-
-
-
setDrawerVisible(false)}
- >
-
-
-
-
setPreviewVisible(false)}
- >
-
-
- {`
-
- ${selectedTags
- .map(
- (tag) =>
- `<${tag.name}${tag.category === "object" ? ' name="obj" value="$data"' : ' name="ctrl" toName="obj"'} />`
- )
- .join("\n ")}
-`}
-
-
-
-
- );
-};
-
-export default VisualTemplateBuilder;
+import React, { useState } from "react";
+import {
+ Card,
+ Button,
+ Space,
+ Row,
+ Col,
+ Drawer,
+ Typography,
+ message,
+} from "antd";
+import {
+ PlusOutlined,
+ EyeOutlined,
+ CodeOutlined,
+ AppstoreOutlined,
+} from "@ant-design/icons";
+import { TagBrowser } from "./components";
+
+const { Paragraph } = Typography;
+
+interface VisualTemplateBuilderProps {
+ onSave?: (templateCode: string) => void;
+}
+
+/**
+ * Visual Template Builder
+ * Provides a drag-and-drop interface for building Label Studio templates
+ */
+const VisualTemplateBuilder: React.FC = ({
+ onSave,
+}) => {
+ const [drawerVisible, setDrawerVisible] = useState(false);
+ const [previewVisible, setPreviewVisible] = useState(false);
+ const [selectedTags, setSelectedTags] = useState<
+ Array<{ name: string; category: "object" | "control" }>
+ >([]);
+
+ const handleTagSelect = (tagName: string, category: "object" | "control") => {
+ message.info(`选择了 ${category === "object" ? "对象" : "控件"}: ${tagName}`);
+ setSelectedTags([...selectedTags, { name: tagName, category }]);
+ setDrawerVisible(false);
+ };
+
+ const handleSave = () => {
+ // TODO: Generate template XML from selectedTags
+ message.success("模板保存成功");
+ onSave?.("");
+ };
+
+ return (
+
+
+
+
+ }
+ onClick={() => setDrawerVisible(true)}
+ >
+ 浏览标签
+
+ }
+ onClick={() => setPreviewVisible(true)}
+ >
+ 查看代码
+
+ }
+ onClick={handleSave}
+ >
+ 保存模板
+
+
+ }
+ >
+
+ {selectedTags.length === 0 ? (
+
+
+ 点击"浏览标签"开始构建您的标注模板
+
+
}
+ onClick={() => setDrawerVisible(true)}
+ >
+ 添加标签
+
+
+ ) : (
+
+ {selectedTags.map((tag, index) => (
+
+
+ {tag.category === "object" ? "对象" : "控件"}: {tag.name}
+
+
+ ))}
+
+ )}
+
+
+
+
+
+
setDrawerVisible(false)}
+ >
+
+
+
+
setPreviewVisible(false)}
+ >
+
+
+ {`
+
+ ${selectedTags
+ .map(
+ (tag) =>
+ `<${tag.name}${tag.category === "object" ? ' name="obj" value="$data"' : ' name="ctrl" toName="obj"'} />`
+ )
+ .join("\n ")}
+`}
+
+
+
+
+ );
+};
+
+export default VisualTemplateBuilder;
diff --git a/frontend/src/pages/DataAnnotation/Template/components/TagBrowser.tsx b/frontend/src/pages/DataAnnotation/Template/components/TagBrowser.tsx
index 173e0c151..cd95e6d4e 100644
--- a/frontend/src/pages/DataAnnotation/Template/components/TagBrowser.tsx
+++ b/frontend/src/pages/DataAnnotation/Template/components/TagBrowser.tsx
@@ -1,260 +1,260 @@
-import React from "react";
-import { Card, Tabs, List, Tag, Typography, Space, Empty, Spin } from "antd";
-import {
- AppstoreOutlined,
- ControlOutlined,
- InfoCircleOutlined,
-} from "@ant-design/icons";
-import { useTagConfig } from "../../../../hooks/useTagConfig";
-import {
- getControlDisplayName,
- getObjectDisplayName,
- getControlGroups,
-} from "../../annotation.tagconfig";
-import type { TagOption } from "../../annotation.tagconfig";
-
-const { Title, Paragraph, Text } = Typography;
-
-interface TagBrowserProps {
- onTagSelect?: (tagName: string, category: "object" | "control") => void;
-}
-
-/**
- * Tag Browser Component
- * Displays all available Label Studio tags in a browsable interface
- */
-const TagBrowser: React.FC = ({ onTagSelect }) => {
- const { config, objectOptions, controlOptions, loading, error } =
- useTagConfig();
-
- if (loading) {
- return (
-
-
-
- );
- }
-
- if (error) {
- return (
-
-
- {error}
- 无法加载标签配置
-
- }
- />
-
- );
- }
-
- const renderObjectList = () => (
- {
- const objConfig = config?.objects[item.value];
- return (
-
- onTagSelect?.(item.value, "object")}
- style={{ height: "100%" }}
- >
-
-
- {getObjectDisplayName(item.value)}
- <{item.value}>
-
-
- {item.description}
-
- {objConfig && (
-
-
- 必需属性:{" "}
- {objConfig.required_attrs.join(", ") || "无"}
-
-
- )}
-
-
-
- );
- }}
- />
- );
-
- const renderControlsByGroup = () => {
- const groups = getControlGroups();
-
- return (
- {
- const groupControls = controlOptions.filter((opt: TagOption) =>
- groupConfig.controls.includes(opt.value)
- );
-
- return {
- key: groupKey,
- label: groupConfig.label,
- children: (
- {
- const ctrlConfig = config?.controls[item.value];
- return (
-
- onTagSelect?.(item.value, "control")}
- style={{ height: "100%" }}
- >
-
-
-
- {getControlDisplayName(item.value)}
-
- <{item.value}>
-
-
- {item.description}
-
- {ctrlConfig && (
-
- {ctrlConfig.requires_children && (
-
- 需要 <{ctrlConfig.child_tag}>
-
- )}
- {ctrlConfig.required_attrs.includes("toName") && (
-
- 绑定对象
-
- )}
-
- )}
-
-
-
- );
- }}
- />
- ),
- };
- })}
- />
- );
- };
-
- return (
-
-
-
- 控件标签 ({controlOptions.length})
-
- ),
- children: renderControlsByGroup(),
- },
- {
- key: "objects",
- label: (
-
-
- 数据对象 ({objectOptions.length})
-
- ),
- children: renderObjectList(),
- },
- {
- key: "help",
- label: (
-
-
- 使用说明
-
- ),
- children: (
-
-
Label Studio 标签配置说明
-
- 标注模板由两类标签组成:
-
-
- -
- 数据对象标签:定义要标注的数据类型(如图像、文本、音频等)
-
- -
- 控件标签:定义标注工具和交互方式(如矩形框、分类选项、文本输入等)
-
-
-
- 基本结构
-
-
-
- {`
-
-
-
-
-
-
-
-
-`}
-
-
-
- 属性说明
-
-
- -
- name:控件的唯一标识符
-
- -
- toName:指向要标注的数据对象的 name
-
- -
- value:数据源字段,以 $ 开头(如 $image, $text)
-
- -
- required:是否必填(可选)
-
-
-
- ),
- },
- ]}
- />
-
- );
-};
-
-export default TagBrowser;
+import React from "react";
+import { Card, Tabs, List, Tag, Typography, Space, Empty, Spin } from "antd";
+import {
+ AppstoreOutlined,
+ ControlOutlined,
+ InfoCircleOutlined,
+} from "@ant-design/icons";
+import { useTagConfig } from "../../../../hooks/useTagConfig";
+import {
+ getControlDisplayName,
+ getObjectDisplayName,
+ getControlGroups,
+} from "../../annotation.tagconfig";
+import type { TagOption } from "../../annotation.tagconfig";
+
+const { Title, Paragraph, Text } = Typography;
+
+interface TagBrowserProps {
+ onTagSelect?: (tagName: string, category: "object" | "control") => void;
+}
+
+/**
+ * Tag Browser Component
+ * Displays all available Label Studio tags in a browsable interface
+ */
+const TagBrowser: React.FC = ({ onTagSelect }) => {
+ const { config, objectOptions, controlOptions, loading, error } =
+ useTagConfig();
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+ {error}
+ 无法加载标签配置
+
+ }
+ />
+
+ );
+ }
+
+ const renderObjectList = () => (
+ {
+ const objConfig = config?.objects[item.value];
+ return (
+
+ onTagSelect?.(item.value, "object")}
+ style={{ height: "100%" }}
+ >
+
+
+ {getObjectDisplayName(item.value)}
+ <{item.value}>
+
+
+ {item.description}
+
+ {objConfig && (
+
+
+ 必需属性:{" "}
+ {objConfig.required_attrs.join(", ") || "无"}
+
+
+ )}
+
+
+
+ );
+ }}
+ />
+ );
+
+ const renderControlsByGroup = () => {
+ const groups = getControlGroups();
+
+ return (
+ {
+ const groupControls = controlOptions.filter((opt: TagOption) =>
+ groupConfig.controls.includes(opt.value)
+ );
+
+ return {
+ key: groupKey,
+ label: groupConfig.label,
+ children: (
+ {
+ const ctrlConfig = config?.controls[item.value];
+ return (
+
+ onTagSelect?.(item.value, "control")}
+ style={{ height: "100%" }}
+ >
+
+
+
+ {getControlDisplayName(item.value)}
+
+ <{item.value}>
+
+
+ {item.description}
+
+ {ctrlConfig && (
+
+ {ctrlConfig.requires_children && (
+
+ 需要 <{ctrlConfig.child_tag}>
+
+ )}
+ {ctrlConfig.required_attrs.includes("toName") && (
+
+ 绑定对象
+
+ )}
+
+ )}
+
+
+
+ );
+ }}
+ />
+ ),
+ };
+ })}
+ />
+ );
+ };
+
+ return (
+
+
+
+ 控件标签 ({controlOptions.length})
+
+ ),
+ children: renderControlsByGroup(),
+ },
+ {
+ key: "objects",
+ label: (
+
+
+ 数据对象 ({objectOptions.length})
+
+ ),
+ children: renderObjectList(),
+ },
+ {
+ key: "help",
+ label: (
+
+
+ 使用说明
+
+ ),
+ children: (
+
+
Label Studio 标签配置说明
+
+ 标注模板由两类标签组成:
+
+
+ -
+ 数据对象标签:定义要标注的数据类型(如图像、文本、音频等)
+
+ -
+ 控件标签:定义标注工具和交互方式(如矩形框、分类选项、文本输入等)
+
+
+
+ 基本结构
+
+
+
+ {`
+
+
+
+
+
+
+
+
+`}
+
+
+
+ 属性说明
+
+
+ -
+ name:控件的唯一标识符
+
+ -
+ toName:指向要标注的数据对象的 name
+
+ -
+ value:数据源字段,以 $ 开头(如 $image, $text)
+
+ -
+ required:是否必填(可选)
+
+
+
+ ),
+ },
+ ]}
+ />
+
+ );
+};
+
+export default TagBrowser;
diff --git a/frontend/src/pages/DataAnnotation/Template/components/TagSelector.tsx b/frontend/src/pages/DataAnnotation/Template/components/TagSelector.tsx
index 2987d37a3..46f748bc8 100644
--- a/frontend/src/pages/DataAnnotation/Template/components/TagSelector.tsx
+++ b/frontend/src/pages/DataAnnotation/Template/components/TagSelector.tsx
@@ -1,301 +1,301 @@
-import React, { useState, useEffect } from "react";
-import { Select, Tooltip, Spin, Collapse, Tag, Space } from "antd";
-import { InfoCircleOutlined } from "@ant-design/icons";
-import { getTagConfigUsingGet } from "../../annotation.api";
-import type {
- LabelStudioTagConfig,
- TagOption,
-} from "../../annotation.tagconfig";
-import {
- parseTagConfig,
- getControlDisplayName,
- getObjectDisplayName,
- getControlGroups,
-} from "../../annotation.tagconfig";
-
-const { Option, OptGroup } = Select;
-
-interface TagSelectorProps {
- value?: string;
- onChange?: (value: string) => void;
- type: "object" | "control";
- placeholder?: string;
- style?: React.CSSProperties;
- disabled?: boolean;
-}
-
-/**
- * Tag Selector Component
- * Dynamically fetches and displays available Label Studio tags from backend config
- */
-const TagSelector: React.FC = ({
- value,
- onChange,
- type,
- placeholder,
- style,
- disabled,
-}) => {
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- const [tagOptions, setTagOptions] = useState([]);
-
- useEffect(() => {
- fetchTagConfig();
- }, []);
-
- const fetchTagConfig = async () => {
- setLoading(true);
- setError(null);
- try {
- const response = await getTagConfigUsingGet();
- if (response.code === 200 && response.data) {
- const config: LabelStudioTagConfig = response.data;
- const { objectOptions, controlOptions } = parseTagConfig(config);
-
- if (type === "object") {
- setTagOptions(objectOptions);
- } else {
- setTagOptions(controlOptions);
- }
- } else {
- setError(response.message || "获取标签配置失败");
- }
- } catch (err: any) {
- console.error("Failed to fetch tag config:", err);
- setError("加载标签配置时出错");
- } finally {
- setLoading(false);
- }
- };
-
- if (loading) {
- return (
- }
- />
- );
- }
-
- if (error) {
- return (
-
-
- );
- }
-
- // Group controls by usage pattern
- if (type === "control") {
- const groups = getControlGroups();
- const groupedOptions: Record = {};
- const ungroupedOptions: TagOption[] = [];
-
- // Group the controls
- Object.entries(groups).forEach(([groupKey, groupConfig]) => {
- groupedOptions[groupKey] = tagOptions.filter((opt) =>
- groupConfig.controls.includes(opt.value)
- );
- });
-
- // Find ungrouped controls
- const allGroupedControls = new Set(
- Object.values(groups).flatMap((g) => g.controls)
- );
- tagOptions.forEach((opt) => {
- if (!allGroupedControls.has(opt.value)) {
- ungroupedOptions.push(opt);
- }
- });
-
- return (
-
- );
- }
-
- // Objects selector (no grouping)
- return (
-
- );
-};
-
-export default TagSelector;
-
-/**
- * Tag Info Panel Component
- * Displays detailed information about a selected tag
- */
-interface TagInfoPanelProps {
- tagConfig: LabelStudioTagConfig | null;
- tagType: string;
- category: "object" | "control";
-}
-
-export const TagInfoPanel: React.FC = ({
- tagConfig,
- tagType,
- category,
-}) => {
- if (!tagConfig || !tagType) {
- return null;
- }
-
- const config =
- category === "object"
- ? tagConfig.objects[tagType]
- : tagConfig.controls[tagType];
-
- if (!config) {
- return null;
- }
-
- return (
-
-
- 描述:
- {config.description}
-
-
-
-
必需属性:
-
- {config.required_attrs.map((attr: string) => (
-
- {attr}
-
- ))}
-
-
-
- {config.optional_attrs &&
- Object.keys(config.optional_attrs).length > 0 && (
-
-
可选属性:
-
- {Object.entries(config.optional_attrs).map(
- ([attrName, attrConfig]: [string, any]) => (
-
- {attrConfig.description && (
- {attrConfig.description}
- )}
- {attrConfig.type && (
- 类型: {attrConfig.type}
- )}
- {attrConfig.default !== undefined && (
- 默认值: {String(attrConfig.default)}
- )}
- {attrConfig.values && (
-
- 可选值: {attrConfig.values.join(", ")}
-
- )}
-
- }
- >
-
- {attrName}
-
-
- )
- )}
-
-
- )}
-
- {config.requires_children && (
-
- 子元素:
- 需要 <{config.child_tag}>
-
- )}
-
- ),
- },
- ]}
- />
- );
-};
+import React, { useState, useEffect } from "react";
+import { Select, Tooltip, Spin, Collapse, Tag, Space } from "antd";
+import { InfoCircleOutlined } from "@ant-design/icons";
+import { getTagConfigUsingGet } from "../../annotation.api";
+import type {
+ LabelStudioTagConfig,
+ TagOption,
+} from "../../annotation.tagconfig";
+import {
+ parseTagConfig,
+ getControlDisplayName,
+ getObjectDisplayName,
+ getControlGroups,
+} from "../../annotation.tagconfig";
+
+const { Option, OptGroup } = Select;
+
+interface TagSelectorProps {
+ value?: string;
+ onChange?: (value: string) => void;
+ type: "object" | "control";
+ placeholder?: string;
+ style?: React.CSSProperties;
+ disabled?: boolean;
+}
+
+/**
+ * Tag Selector Component
+ * Dynamically fetches and displays available Label Studio tags from backend config
+ */
+const TagSelector: React.FC = ({
+ value,
+ onChange,
+ type,
+ placeholder,
+ style,
+ disabled,
+}) => {
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [tagOptions, setTagOptions] = useState([]);
+
+ useEffect(() => {
+ fetchTagConfig();
+ }, []);
+
+ const fetchTagConfig = async () => {
+ setLoading(true);
+ setError(null);
+ try {
+ const response = await getTagConfigUsingGet();
+ if (response.code === 200 && response.data) {
+ const config: LabelStudioTagConfig = response.data;
+ const { objectOptions, controlOptions } = parseTagConfig(config);
+
+ if (type === "object") {
+ setTagOptions(objectOptions);
+ } else {
+ setTagOptions(controlOptions);
+ }
+ } else {
+ setError(response.message || "获取标签配置失败");
+ }
+ } catch (err: any) {
+ console.error("Failed to fetch tag config:", err);
+ setError("加载标签配置时出错");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ if (loading) {
+ return (
+ }
+ />
+ );
+ }
+
+ if (error) {
+ return (
+
+
+ );
+ }
+
+ // Group controls by usage pattern
+ if (type === "control") {
+ const groups = getControlGroups();
+ const groupedOptions: Record = {};
+ const ungroupedOptions: TagOption[] = [];
+
+ // Group the controls
+ Object.entries(groups).forEach(([groupKey, groupConfig]) => {
+ groupedOptions[groupKey] = tagOptions.filter((opt) =>
+ groupConfig.controls.includes(opt.value)
+ );
+ });
+
+ // Find ungrouped controls
+ const allGroupedControls = new Set(
+ Object.values(groups).flatMap((g) => g.controls)
+ );
+ tagOptions.forEach((opt) => {
+ if (!allGroupedControls.has(opt.value)) {
+ ungroupedOptions.push(opt);
+ }
+ });
+
+ return (
+
+ );
+ }
+
+ // Objects selector (no grouping)
+ return (
+
+ );
+};
+
+export default TagSelector;
+
+/**
+ * Tag Info Panel Component
+ * Displays detailed information about a selected tag
+ */
+interface TagInfoPanelProps {
+ tagConfig: LabelStudioTagConfig | null;
+ tagType: string;
+ category: "object" | "control";
+}
+
+export const TagInfoPanel: React.FC = ({
+ tagConfig,
+ tagType,
+ category,
+}) => {
+ if (!tagConfig || !tagType) {
+ return null;
+ }
+
+ const config =
+ category === "object"
+ ? tagConfig.objects[tagType]
+ : tagConfig.controls[tagType];
+
+ if (!config) {
+ return null;
+ }
+
+ return (
+
+
+ 描述:
+ {config.description}
+
+
+
+
必需属性:
+
+ {config.required_attrs.map((attr: string) => (
+
+ {attr}
+
+ ))}
+
+
+
+ {config.optional_attrs &&
+ Object.keys(config.optional_attrs).length > 0 && (
+
+
可选属性:
+
+ {Object.entries(config.optional_attrs).map(
+ ([attrName, attrConfig]: [string, any]) => (
+
+ {attrConfig.description && (
+ {attrConfig.description}
+ )}
+ {attrConfig.type && (
+ 类型: {attrConfig.type}
+ )}
+ {attrConfig.default !== undefined && (
+ 默认值: {String(attrConfig.default)}
+ )}
+ {attrConfig.values && (
+
+ 可选值: {attrConfig.values.join(", ")}
+
+ )}
+
+ }
+ >
+
+ {attrName}
+
+
+ )
+ )}
+
+
+ )}
+
+ {config.requires_children && (
+
+ 子元素:
+ 需要 <{config.child_tag}>
+
+ )}
+
+ ),
+ },
+ ]}
+ />
+ );
+};
diff --git a/frontend/src/pages/DataAnnotation/Template/components/index.ts b/frontend/src/pages/DataAnnotation/Template/components/index.ts
index f740898f5..09d55ec15 100644
--- a/frontend/src/pages/DataAnnotation/Template/components/index.ts
+++ b/frontend/src/pages/DataAnnotation/Template/components/index.ts
@@ -1,3 +1,3 @@
-export { default as TagSelector } from "./TagSelector";
-export { default as TagBrowser } from "./TagBrowser";
-export { TagInfoPanel } from "./TagSelector";
+export { default as TagSelector } from "./TagSelector";
+export { default as TagBrowser } from "./TagBrowser";
+export { TagInfoPanel } from "./TagSelector";
diff --git a/frontend/src/pages/DataAnnotation/Template/index.ts b/frontend/src/pages/DataAnnotation/Template/index.ts
index cbc843d91..1dac9e51b 100644
--- a/frontend/src/pages/DataAnnotation/Template/index.ts
+++ b/frontend/src/pages/DataAnnotation/Template/index.ts
@@ -1,4 +1,4 @@
-export { default as TemplateList } from "./TemplateList";
-export { default as TemplateForm } from "./TemplateForm";
-export { default as TemplateDetail } from "./TemplateDetail";
-export { TagBrowser, TagSelector, TagInfoPanel } from "./components";
+export { default as TemplateList } from "./TemplateList";
+export { default as TemplateForm } from "./TemplateForm";
+export { default as TemplateDetail } from "./TemplateDetail";
+export { TagBrowser, TagSelector, TagInfoPanel } from "./components";
diff --git a/frontend/src/pages/DataAnnotation/annotation.api.ts b/frontend/src/pages/DataAnnotation/annotation.api.ts
index 8763723bb..46d214456 100644
--- a/frontend/src/pages/DataAnnotation/annotation.api.ts
+++ b/frontend/src/pages/DataAnnotation/annotation.api.ts
@@ -1,50 +1,50 @@
-import { get, post, put, del } from "@/utils/request";
-
-// 标注任务管理相关接口
-export function queryAnnotationTasksUsingGet(params?: any) {
- return get("/api/annotation/project", params);
-}
-
-export function createAnnotationTaskUsingPost(data: any) {
- return post("/api/annotation/project", data);
-}
-
-export function syncAnnotationTaskUsingPost(data: any) {
- return post(`/api/annotation/task/sync`, data);
-}
-
-export function deleteAnnotationTaskByIdUsingDelete(mappingId: string) {
- // Backend expects mapping UUID as path parameter
- return del(`/api/annotation/project/${mappingId}`);
-}
-
-export function loginAnnotationUsingGet(mappingId: string) {
- return get("/api/annotation/project/${mappingId}/login");
-}
-
-// 标签配置管理
-export function getTagConfigUsingGet() {
- return get("/api/annotation/tags/config");
-}
-
-// 标注模板管理
-export function queryAnnotationTemplatesUsingGet(params?: any) {
- return get("/api/annotation/template", params);
-}
-
-export function createAnnotationTemplateUsingPost(data: any) {
- return post("/api/annotation/template", data);
-}
-
-export function updateAnnotationTemplateByIdUsingPut(
- templateId: string | number,
- data: any
-) {
- return put(`/api/annotation/template/${templateId}`, data);
-}
-
-export function deleteAnnotationTemplateByIdUsingDelete(
- templateId: string | number
-) {
- return del(`/api/annotation/template/${templateId}`);
-}
+import { get, post, put, del } from "@/utils/request";
+
+// 标注任务管理相关接口
+export function queryAnnotationTasksUsingGet(params?: any) {
+ return get("/api/annotation/project", params);
+}
+
+export function createAnnotationTaskUsingPost(data: any) {
+ return post("/api/annotation/project", data);
+}
+
+export function syncAnnotationTaskUsingPost(data: any) {
+ return post(`/api/annotation/task/sync`, data);
+}
+
+export function deleteAnnotationTaskByIdUsingDelete(mappingId: string) {
+ // Backend expects mapping UUID as path parameter
+ return del(`/api/annotation/project/${mappingId}`);
+}
+
+export function loginAnnotationUsingGet(mappingId: string) {
+ return get("/api/annotation/project/${mappingId}/login");
+}
+
+// 标签配置管理
+export function getTagConfigUsingGet() {
+ return get("/api/annotation/tags/config");
+}
+
+// 标注模板管理
+export function queryAnnotationTemplatesUsingGet(params?: any) {
+ return get("/api/annotation/template", params);
+}
+
+export function createAnnotationTemplateUsingPost(data: any) {
+ return post("/api/annotation/template", data);
+}
+
+export function updateAnnotationTemplateByIdUsingPut(
+ templateId: string | number,
+ data: any
+) {
+ return put(`/api/annotation/template/${templateId}`, data);
+}
+
+export function deleteAnnotationTemplateByIdUsingDelete(
+ templateId: string | number
+) {
+ return del(`/api/annotation/template/${templateId}`);
+}
diff --git a/frontend/src/pages/DataAnnotation/annotation.const.tsx b/frontend/src/pages/DataAnnotation/annotation.const.tsx
index 4b597d985..a60b0558c 100644
--- a/frontend/src/pages/DataAnnotation/annotation.const.tsx
+++ b/frontend/src/pages/DataAnnotation/annotation.const.tsx
@@ -1,140 +1,140 @@
-import { StickyNote } from "lucide-react";
-import {AnnotationTaskStatus, AnnotationType, Classification, DataType, TemplateType} from "./annotation.model";
-import {
- CheckCircleOutlined,
- ClockCircleOutlined,
- CloseCircleOutlined,
-} from "@ant-design/icons";
-
-export const AnnotationTaskStatusMap = {
- [AnnotationTaskStatus.ACTIVE]: {
- label: "活跃",
- value: AnnotationTaskStatus.ACTIVE,
- color: "#409f17ff",
- icon: ,
- },
- [AnnotationTaskStatus.PROCESSING]: {
- label: "处理中",
- value: AnnotationTaskStatus.PROCESSING,
- color: "#2673e5",
- icon: ,
- },
- [AnnotationTaskStatus.INACTIVE]: {
- label: "未激活",
- value: AnnotationTaskStatus.INACTIVE,
- color: "#4f4444ff",
- icon: ,
- },
-};
-
-export function mapAnnotationTask(task: any) {
- // Normalize labeling project id from possible backend field names
- const labelingProjId = task?.labelingProjId || task?.labelingProjectId || task?.projId || task?.labeling_project_id || "";
-
- const statsArray = task?.statistics
- ? [
- { label: "准确率", value: task.statistics.accuracy ?? "-" },
- { label: "平均时长", value: task.statistics.averageTime ?? "-" },
- { label: "待复核", value: task.statistics.reviewCount ?? "-" },
- ]
- : [];
-
- return {
- ...task,
- id: task.id,
- // provide consistent field for components
- labelingProjId,
- projId: labelingProjId,
- name: task.name,
- description: task.description || "",
- datasetName: task.datasetName || task.dataset_name || "-",
- createdAt: task.createdAt || task.created_at || "-",
- updatedAt: task.updatedAt || task.updated_at || "-",
- icon: ,
- iconColor: "bg-blue-100",
- status: {
- label:
- task.status === "completed"
- ? "已完成"
- : task.status === "processing"
- ? "进行中"
- : task.status === "skipped"
- ? "已跳过"
- : "待开始",
- color: "bg-blue-100",
- },
- statistics: statsArray,
- };
-}
-
-export const DataTypeMap = {
- [DataType.TEXT]: {
- label: "文本",
- value: DataType.TEXT
- },
- [DataType.IMAGE]: {
- label: "图片",
- value: DataType.IMAGE
- },
- [DataType.AUDIO]: {
- label: "音频",
- value: DataType.AUDIO
- },
- [DataType.VIDEO]: {
- label: "视频",
- value: DataType.VIDEO
- },
-}
-
-export const ClassificationMap = {
- [Classification.COMPUTER_VERSION]: {
- label: "计算机视觉",
- value: Classification.COMPUTER_VERSION
- },
- [Classification.NLP]: {
- label: "自然语言处理",
- value: Classification.NLP
- },
- [Classification.AUDIO]: {
- label: "音频",
- value: Classification.AUDIO
- },
- [Classification.QUALITY_CONTROL]: {
- label: "质量控制",
- value: Classification.QUALITY_CONTROL
- },
- [Classification.CUSTOM]: {
- label: "自定义",
- value: Classification.CUSTOM
- },
-}
-
-export const AnnotationTypeMap = {
- [AnnotationType.CLASSIFICATION]: {
- label: "分类",
- value: AnnotationType.CLASSIFICATION
- },
- [AnnotationType.OBJECT_DETECTION]: {
- label: "目标检测",
- value: AnnotationType.OBJECT_DETECTION
- },
- [AnnotationType.SEGMENTATION]: {
- label: "分割",
- value: AnnotationType.SEGMENTATION
- },
- [AnnotationType.NER]: {
- label: "命名实体识别",
- value: AnnotationType.NER
- },
-}
-
-export const TemplateTypeMap = {
- [TemplateType.SYSTEM]: {
- label: "系统内置",
- value: TemplateType.SYSTEM
- },
- [TemplateType.CUSTOM]: {
- label: "自定义",
- value: TemplateType.CUSTOM
- },
+import { StickyNote } from "lucide-react";
+import {AnnotationTaskStatus, AnnotationType, Classification, DataType, TemplateType} from "./annotation.model";
+import {
+ CheckCircleOutlined,
+ ClockCircleOutlined,
+ CloseCircleOutlined,
+} from "@ant-design/icons";
+
+export const AnnotationTaskStatusMap = {
+ [AnnotationTaskStatus.ACTIVE]: {
+ label: "活跃",
+ value: AnnotationTaskStatus.ACTIVE,
+ color: "#409f17ff",
+ icon: ,
+ },
+ [AnnotationTaskStatus.PROCESSING]: {
+ label: "处理中",
+ value: AnnotationTaskStatus.PROCESSING,
+ color: "#2673e5",
+ icon: ,
+ },
+ [AnnotationTaskStatus.INACTIVE]: {
+ label: "未激活",
+ value: AnnotationTaskStatus.INACTIVE,
+ color: "#4f4444ff",
+ icon: ,
+ },
+};
+
+export function mapAnnotationTask(task: any) {
+ // Normalize labeling project id from possible backend field names
+ const labelingProjId = task?.labelingProjId || task?.labelingProjectId || task?.projId || task?.labeling_project_id || "";
+
+ const statsArray = task?.statistics
+ ? [
+ { label: "准确率", value: task.statistics.accuracy ?? "-" },
+ { label: "平均时长", value: task.statistics.averageTime ?? "-" },
+ { label: "待复核", value: task.statistics.reviewCount ?? "-" },
+ ]
+ : [];
+
+ return {
+ ...task,
+ id: task.id,
+ // provide consistent field for components
+ labelingProjId,
+ projId: labelingProjId,
+ name: task.name,
+ description: task.description || "",
+ datasetName: task.datasetName || task.dataset_name || "-",
+ createdAt: task.createdAt || task.created_at || "-",
+ updatedAt: task.updatedAt || task.updated_at || "-",
+ icon: ,
+ iconColor: "bg-blue-100",
+ status: {
+ label:
+ task.status === "completed"
+ ? "已完成"
+ : task.status === "processing"
+ ? "进行中"
+ : task.status === "skipped"
+ ? "已跳过"
+ : "待开始",
+ color: "bg-blue-100",
+ },
+ statistics: statsArray,
+ };
+}
+
+export const DataTypeMap = {
+ [DataType.TEXT]: {
+ label: "文本",
+ value: DataType.TEXT
+ },
+ [DataType.IMAGE]: {
+ label: "图片",
+ value: DataType.IMAGE
+ },
+ [DataType.AUDIO]: {
+ label: "音频",
+ value: DataType.AUDIO
+ },
+ [DataType.VIDEO]: {
+ label: "视频",
+ value: DataType.VIDEO
+ },
+}
+
+export const ClassificationMap = {
+ [Classification.COMPUTER_VERSION]: {
+ label: "计算机视觉",
+ value: Classification.COMPUTER_VERSION
+ },
+ [Classification.NLP]: {
+ label: "自然语言处理",
+ value: Classification.NLP
+ },
+ [Classification.AUDIO]: {
+ label: "音频",
+ value: Classification.AUDIO
+ },
+ [Classification.QUALITY_CONTROL]: {
+ label: "质量控制",
+ value: Classification.QUALITY_CONTROL
+ },
+ [Classification.CUSTOM]: {
+ label: "自定义",
+ value: Classification.CUSTOM
+ },
+}
+
+export const AnnotationTypeMap = {
+ [AnnotationType.CLASSIFICATION]: {
+ label: "分类",
+ value: AnnotationType.CLASSIFICATION
+ },
+ [AnnotationType.OBJECT_DETECTION]: {
+ label: "目标检测",
+ value: AnnotationType.OBJECT_DETECTION
+ },
+ [AnnotationType.SEGMENTATION]: {
+ label: "分割",
+ value: AnnotationType.SEGMENTATION
+ },
+ [AnnotationType.NER]: {
+ label: "命名实体识别",
+ value: AnnotationType.NER
+ },
+}
+
+export const TemplateTypeMap = {
+ [TemplateType.SYSTEM]: {
+ label: "系统内置",
+ value: TemplateType.SYSTEM
+ },
+ [TemplateType.CUSTOM]: {
+ label: "自定义",
+ value: TemplateType.CUSTOM
+ },
}
\ No newline at end of file
diff --git a/frontend/src/pages/DataAnnotation/annotation.model.ts b/frontend/src/pages/DataAnnotation/annotation.model.ts
index ac310d5a8..3a751d660 100644
--- a/frontend/src/pages/DataAnnotation/annotation.model.ts
+++ b/frontend/src/pages/DataAnnotation/annotation.model.ts
@@ -1,107 +1,107 @@
-import type { DatasetType } from "@/pages/DataManagement/dataset.model";
-
-export enum AnnotationTaskStatus {
- ACTIVE = "active",
- INACTIVE = "inactive",
- PROCESSING = "processing",
- COMPLETED = "completed",
- SKIPPED = "skipped",
-}
-
-export interface AnnotationTask {
- id: string;
- name: string;
- labelingProjId: string;
- datasetId: string;
-
- annotationCount: number;
-
- description?: string;
- assignedTo?: string;
- progress: number;
- statistics: {
- accuracy: number;
- averageTime: number;
- reviewCount: number;
- };
- status: AnnotationTaskStatus;
- totalDataCount: number;
- type: DatasetType;
-
- createdAt: string;
- updatedAt: string;
-}
-
-// 标注模板相关类型
-export interface LabelDefinition {
- fromName: string;
- toName: string;
- type: string;
- options?: string[];
- labels?: string[];
- required?: boolean;
- description?: string;
-}
-
-export interface ObjectDefinition {
- name: string;
- type: string;
- value: string;
-}
-
-export interface TemplateConfiguration {
- labels: LabelDefinition[];
- objects: ObjectDefinition[];
- metadata?: Record;
-}
-
-export interface AnnotationTemplate {
- id: string;
- name: string;
- description?: string;
- dataType: string;
- labelingType: string;
- configuration: TemplateConfiguration;
- labelConfig?: string;
- style: string;
- category: string;
- builtIn: boolean;
- version: string;
- createdAt: string;
- updatedAt?: string;
-}
-
-export interface AnnotationTemplateListResponse {
- content: AnnotationTemplate[];
- total: number;
- page: number;
- size: number;
- totalPages: number;
-}
-
-export enum DataType {
- TEXT = "text",
- IMAGE = "image",
- AUDIO = "audio",
- VIDEO = "video",
-}
-
-export enum Classification {
- COMPUTER_VERSION = "computer-vision",
- NLP = "nlp",
- AUDIO = "audio",
- QUALITY_CONTROL = "quality-control",
- CUSTOM = "custom"
-}
-
-export enum AnnotationType {
- CLASSIFICATION = "classification",
- OBJECT_DETECTION = "object-detection",
- SEGMENTATION = "segmentation",
- NER = "ner"
-}
-
-export enum TemplateType {
- SYSTEM = "true",
- CUSTOM = "false"
-}
+import type { DatasetType } from "@/pages/DataManagement/dataset.model";
+
+export enum AnnotationTaskStatus {
+ ACTIVE = "active",
+ INACTIVE = "inactive",
+ PROCESSING = "processing",
+ COMPLETED = "completed",
+ SKIPPED = "skipped",
+}
+
+export interface AnnotationTask {
+ id: string;
+ name: string;
+ labelingProjId: string;
+ datasetId: string;
+
+ annotationCount: number;
+
+ description?: string;
+ assignedTo?: string;
+ progress: number;
+ statistics: {
+ accuracy: number;
+ averageTime: number;
+ reviewCount: number;
+ };
+ status: AnnotationTaskStatus;
+ totalDataCount: number;
+ type: DatasetType;
+
+ createdAt: string;
+ updatedAt: string;
+}
+
+// 标注模板相关类型
+export interface LabelDefinition {
+ fromName: string;
+ toName: string;
+ type: string;
+ options?: string[];
+ labels?: string[];
+ required?: boolean;
+ description?: string;
+}
+
+export interface ObjectDefinition {
+ name: string;
+ type: string;
+ value: string;
+}
+
+export interface TemplateConfiguration {
+ labels: LabelDefinition[];
+ objects: ObjectDefinition[];
+ metadata?: Record;
+}
+
+export interface AnnotationTemplate {
+ id: string;
+ name: string;
+ description?: string;
+ dataType: string;
+ labelingType: string;
+ configuration: TemplateConfiguration;
+ labelConfig?: string;
+ style: string;
+ category: string;
+ builtIn: boolean;
+ version: string;
+ createdAt: string;
+ updatedAt?: string;
+}
+
+export interface AnnotationTemplateListResponse {
+ content: AnnotationTemplate[];
+ total: number;
+ page: number;
+ size: number;
+ totalPages: number;
+}
+
+export enum DataType {
+ TEXT = "text",
+ IMAGE = "image",
+ AUDIO = "audio",
+ VIDEO = "video",
+}
+
+export enum Classification {
+ COMPUTER_VERSION = "computer-vision",
+ NLP = "nlp",
+ AUDIO = "audio",
+ QUALITY_CONTROL = "quality-control",
+ CUSTOM = "custom"
+}
+
+export enum AnnotationType {
+ CLASSIFICATION = "classification",
+ OBJECT_DETECTION = "object-detection",
+ SEGMENTATION = "segmentation",
+ NER = "ner"
+}
+
+export enum TemplateType {
+ SYSTEM = "true",
+ CUSTOM = "false"
+}
diff --git a/frontend/src/pages/DataAnnotation/annotation.tagconfig.ts b/frontend/src/pages/DataAnnotation/annotation.tagconfig.ts
index 8c1509ccf..85dff00ee 100644
--- a/frontend/src/pages/DataAnnotation/annotation.tagconfig.ts
+++ b/frontend/src/pages/DataAnnotation/annotation.tagconfig.ts
@@ -1,187 +1,187 @@
-/**
- * Label Studio Tag Configuration Types
- * Corresponds to runtime/datamate-python/app/module/annotation/config/label_studio_tags.yaml
- */
-
-export interface TagAttributeConfig {
- type?: "boolean" | "number" | "string";
- values?: string[];
- default?: any;
- description?: string;
-}
-
-export interface TagConfig {
- description: string;
- required_attrs: string[];
- optional_attrs?: Record;
- requires_children?: boolean;
- child_tag?: string;
- child_required_attrs?: string[];
- category?: string; // e.g., "labeling" or "layout" for controls; "image", "text", etc. for objects
-}
-
-export interface LabelStudioTagConfig {
- objects: Record;
- controls: Record;
-}
-
-/**
- * UI-friendly representation of a tag for selection
- */
-export interface TagOption {
- value: string;
- label: string;
- description: string;
- category: "object" | "control";
- requiresChildren: boolean;
- childTag?: string;
- requiredAttrs: string[];
- optionalAttrs?: Record;
-}
-
-/**
- * Convert backend tag config to frontend tag options
- * @param config - The full tag configuration from backend
- * @param includeLabelingOnly - If true, only include controls with category="labeling" (default: true)
- */
-export function parseTagConfig(
- config: LabelStudioTagConfig,
- includeLabelingOnly: boolean = true
-): {
- objectOptions: TagOption[];
- controlOptions: TagOption[];
-} {
- const objectOptions: TagOption[] = Object.entries(config.objects).map(
- ([key, value]) => ({
- value: key,
- label: key,
- description: value.description,
- category: "object" as const,
- requiresChildren: value.requires_children || false,
- childTag: value.child_tag,
- requiredAttrs: value.required_attrs,
- optionalAttrs: value.optional_attrs,
- })
- );
-
- const controlOptions: TagOption[] = Object.entries(config.controls)
- .filter(([_, value]) => {
- // If includeLabelingOnly is true, filter out layout controls
- if (includeLabelingOnly) {
- return value.category === "labeling";
- }
- return true;
- })
- .map(([key, value]) => ({
- value: key,
- label: key,
- description: value.description,
- category: "control" as const,
- requiresChildren: value.requires_children || false,
- childTag: value.child_tag,
- requiredAttrs: value.required_attrs,
- optionalAttrs: value.optional_attrs,
- }));
-
- return { objectOptions, controlOptions };
-}
-
-/**
- * Get user-friendly display name for control types
- */
-export function getControlDisplayName(controlType: string): string {
- const displayNames: Record = {
- Choices: "选项 (单选/多选)",
- RectangleLabels: "矩形框",
- PolygonLabels: "多边形",
- Labels: "标签",
- TextArea: "文本区域",
- Rating: "评分",
- Taxonomy: "分类树",
- Ranker: "排序",
- List: "列表",
- BrushLabels: "画笔分割",
- EllipseLabels: "椭圆",
- KeyPointLabels: "关键点",
- Rectangle: "矩形",
- Polygon: "多边形",
- Ellipse: "椭圆",
- KeyPoint: "关键点",
- Brush: "画笔",
- Number: "数字输入",
- DateTime: "日期时间",
- Relation: "关系",
- Relations: "关系组",
- Pairwise: "成对比较",
- };
-
- return displayNames[controlType] || controlType;
-}
-
-/**
- * Get user-friendly display name for object types
- */
-export function getObjectDisplayName(objectType: string): string {
- const displayNames: Record = {
- Image: "图像",
- Text: "文本",
- Audio: "音频",
- Video: "视频",
- HyperText: "HTML内容",
- PDF: "PDF文档",
- Markdown: "Markdown内容",
- Paragraphs: "段落",
- Table: "表格",
- AudioPlus: "高级音频",
- Timeseries: "时间序列",
- Vector: "向量数据",
- Chat: "对话数据",
- };
-
- return displayNames[objectType] || objectType;
-}
-
-/**
- * Group control types by common usage patterns
- */
-export function getControlGroups(): Record<
- string,
- { label: string; controls: string[] }
-> {
- return {
- classification: {
- label: "分类标注",
- controls: ["Choices", "Taxonomy", "Labels", "Rating"],
- },
- detection: {
- label: "目标检测",
- controls: [
- "RectangleLabels",
- "PolygonLabels",
- "EllipseLabels",
- "KeyPointLabels",
- "Rectangle",
- "Polygon",
- "Ellipse",
- "KeyPoint",
- ],
- },
- segmentation: {
- label: "分割标注",
- controls: ["BrushLabels", "Brush", "BitmaskLabels", "MagicWand"],
- },
- text: {
- label: "文本输入",
- controls: ["TextArea", "Number", "DateTime"],
- },
- other: {
- label: "其他",
- controls: [
- "TimeseriesLabels",
- "VectorLabels",
- "ParagraphLabels",
- "VideoRectangle",
- ],
- },
- };
-}
+/**
+ * Label Studio Tag Configuration Types
+ * Corresponds to runtime/datamate-python/app/module/annotation/config/label_studio_tags.yaml
+ */
+
+export interface TagAttributeConfig {
+ type?: "boolean" | "number" | "string";
+ values?: string[];
+ default?: any;
+ description?: string;
+}
+
+export interface TagConfig {
+ description: string;
+ required_attrs: string[];
+ optional_attrs?: Record;
+ requires_children?: boolean;
+ child_tag?: string;
+ child_required_attrs?: string[];
+ category?: string; // e.g., "labeling" or "layout" for controls; "image", "text", etc. for objects
+}
+
+export interface LabelStudioTagConfig {
+ objects: Record;
+ controls: Record;
+}
+
+/**
+ * UI-friendly representation of a tag for selection
+ */
+export interface TagOption {
+ value: string;
+ label: string;
+ description: string;
+ category: "object" | "control";
+ requiresChildren: boolean;
+ childTag?: string;
+ requiredAttrs: string[];
+ optionalAttrs?: Record;
+}
+
+/**
+ * Convert backend tag config to frontend tag options
+ * @param config - The full tag configuration from backend
+ * @param includeLabelingOnly - If true, only include controls with category="labeling" (default: true)
+ */
+export function parseTagConfig(
+ config: LabelStudioTagConfig,
+ includeLabelingOnly: boolean = true
+): {
+ objectOptions: TagOption[];
+ controlOptions: TagOption[];
+} {
+ const objectOptions: TagOption[] = Object.entries(config.objects).map(
+ ([key, value]) => ({
+ value: key,
+ label: key,
+ description: value.description,
+ category: "object" as const,
+ requiresChildren: value.requires_children || false,
+ childTag: value.child_tag,
+ requiredAttrs: value.required_attrs,
+ optionalAttrs: value.optional_attrs,
+ })
+ );
+
+ const controlOptions: TagOption[] = Object.entries(config.controls)
+ .filter(([_, value]) => {
+ // If includeLabelingOnly is true, filter out layout controls
+ if (includeLabelingOnly) {
+ return value.category === "labeling";
+ }
+ return true;
+ })
+ .map(([key, value]) => ({
+ value: key,
+ label: key,
+ description: value.description,
+ category: "control" as const,
+ requiresChildren: value.requires_children || false,
+ childTag: value.child_tag,
+ requiredAttrs: value.required_attrs,
+ optionalAttrs: value.optional_attrs,
+ }));
+
+ return { objectOptions, controlOptions };
+}
+
+/**
+ * Get user-friendly display name for control types
+ */
+export function getControlDisplayName(controlType: string): string {
+ const displayNames: Record = {
+ Choices: "选项 (单选/多选)",
+ RectangleLabels: "矩形框",
+ PolygonLabels: "多边形",
+ Labels: "标签",
+ TextArea: "文本区域",
+ Rating: "评分",
+ Taxonomy: "分类树",
+ Ranker: "排序",
+ List: "列表",
+ BrushLabels: "画笔分割",
+ EllipseLabels: "椭圆",
+ KeyPointLabels: "关键点",
+ Rectangle: "矩形",
+ Polygon: "多边形",
+ Ellipse: "椭圆",
+ KeyPoint: "关键点",
+ Brush: "画笔",
+ Number: "数字输入",
+ DateTime: "日期时间",
+ Relation: "关系",
+ Relations: "关系组",
+ Pairwise: "成对比较",
+ };
+
+ return displayNames[controlType] || controlType;
+}
+
+/**
+ * Get user-friendly display name for object types
+ */
+export function getObjectDisplayName(objectType: string): string {
+ const displayNames: Record = {
+ Image: "图像",
+ Text: "文本",
+ Audio: "音频",
+ Video: "视频",
+ HyperText: "HTML内容",
+ PDF: "PDF文档",
+ Markdown: "Markdown内容",
+ Paragraphs: "段落",
+ Table: "表格",
+ AudioPlus: "高级音频",
+ Timeseries: "时间序列",
+ Vector: "向量数据",
+ Chat: "对话数据",
+ };
+
+ return displayNames[objectType] || objectType;
+}
+
+/**
+ * Group control types by common usage patterns
+ */
+export function getControlGroups(): Record<
+ string,
+ { label: string; controls: string[] }
+> {
+ return {
+ classification: {
+ label: "分类标注",
+ controls: ["Choices", "Taxonomy", "Labels", "Rating"],
+ },
+ detection: {
+ label: "目标检测",
+ controls: [
+ "RectangleLabels",
+ "PolygonLabels",
+ "EllipseLabels",
+ "KeyPointLabels",
+ "Rectangle",
+ "Polygon",
+ "Ellipse",
+ "KeyPoint",
+ ],
+ },
+ segmentation: {
+ label: "分割标注",
+ controls: ["BrushLabels", "Brush", "BitmaskLabels", "MagicWand"],
+ },
+ text: {
+ label: "文本输入",
+ controls: ["TextArea", "Number", "DateTime"],
+ },
+ other: {
+ label: "其他",
+ controls: [
+ "TimeseriesLabels",
+ "VectorLabels",
+ "ParagraphLabels",
+ "VideoRectangle",
+ ],
+ },
+ };
+}
diff --git a/frontend/src/pages/DataCleansing/Create/CreateTask.tsx b/frontend/src/pages/DataCleansing/Create/CreateTask.tsx
index b1c89d833..b0fbe5765 100644
--- a/frontend/src/pages/DataCleansing/Create/CreateTask.tsx
+++ b/frontend/src/pages/DataCleansing/Create/CreateTask.tsx
@@ -1,133 +1,133 @@
-import { useState } from "react";
-import { Steps, Button, message, Form } from "antd";
-import { SaveOutlined } from "@ant-design/icons";
-import { Link, useNavigate } from "react-router";
-import { ArrowLeft } from "lucide-react";
-import { createCleaningTaskUsingPost } from "../cleansing.api";
-import CreateTaskStepOne from "./components/CreateTaskStepOne";
-import { useCreateStepTwo } from "./hooks/useCreateStepTwo";
-import { DatasetType } from "@/pages/DataManagement/dataset.model";
-
-export default function CleansingTaskCreate() {
- const navigate = useNavigate();
- const [form] = Form.useForm();
- const [taskConfig, setTaskConfig] = useState({
- name: "",
- description: "",
- srcDatasetId: "",
- srcDatasetName: "",
- destDatasetName: "",
- destDatasetType: DatasetType.TEXT,
- type: DatasetType.TEXT,
- });
-
- const {
- renderStepTwo,
- selectedOperators,
- currentStep,
- handlePrev,
- handleNext,
- } = useCreateStepTwo();
-
- const handleSave = async () => {
- const task = {
- ...taskConfig,
- instance: selectedOperators.map((item) => ({
- id: item.id,
- overrides: {
- ...item.defaultParams,
- ...item.overrides,
- },
- inputs: item.inputs,
- outputs: item.outputs,
- })),
- };
- navigate("/data/cleansing?view=task");
- await createCleaningTaskUsingPost(task);
- message.success("任务已创建");
- };
-
- const canProceed = () => {
- switch (currentStep) {
- case 1: {
- const values = form.getFieldsValue();
- return (
- values.name &&
- values.srcDatasetId &&
- values.destDatasetName &&
- values.destDatasetType
- );
- }
- case 2:
- return selectedOperators.length > 0;
- default:
- return false;
- }
- };
-
- const renderStepContent = () => {
- switch (currentStep) {
- case 1:
- return (
-
- );
- case 2:
- return renderStepTwo;
- default:
- return null;
- }
- };
-
- return (
-
- {/* Header */}
-
- {/* Step Content */}
-
-
{renderStepContent()}
-
- navigate("/data/cleansing")}>取消
- {currentStep > 1 && 上一步}
- {currentStep === 2 ? (
- }
- onClick={handleSave}
- disabled={!canProceed()}
- >
- 创建任务
-
- ) : (
-
- 下一步
-
- )}
-
-
-
- );
-}
+import { useState } from "react";
+import { Steps, Button, message, Form } from "antd";
+import { SaveOutlined } from "@ant-design/icons";
+import { Link, useNavigate } from "react-router";
+import { ArrowLeft } from "lucide-react";
+import { createCleaningTaskUsingPost } from "../cleansing.api";
+import CreateTaskStepOne from "./components/CreateTaskStepOne";
+import { useCreateStepTwo } from "./hooks/useCreateStepTwo";
+import { DatasetType } from "@/pages/DataManagement/dataset.model";
+
+export default function CleansingTaskCreate() {
+ const navigate = useNavigate();
+ const [form] = Form.useForm();
+ const [taskConfig, setTaskConfig] = useState({
+ name: "",
+ description: "",
+ srcDatasetId: "",
+ srcDatasetName: "",
+ destDatasetName: "",
+ destDatasetType: DatasetType.TEXT,
+ type: DatasetType.TEXT,
+ });
+
+ const {
+ renderStepTwo,
+ selectedOperators,
+ currentStep,
+ handlePrev,
+ handleNext,
+ } = useCreateStepTwo();
+
+ const handleSave = async () => {
+ const task = {
+ ...taskConfig,
+ instance: selectedOperators.map((item) => ({
+ id: item.id,
+ overrides: {
+ ...item.defaultParams,
+ ...item.overrides,
+ },
+ inputs: item.inputs,
+ outputs: item.outputs,
+ })),
+ };
+ navigate("/data/cleansing?view=task");
+ await createCleaningTaskUsingPost(task);
+ message.success("任务已创建");
+ };
+
+ const canProceed = () => {
+ switch (currentStep) {
+ case 1: {
+ const values = form.getFieldsValue();
+ return (
+ values.name &&
+ values.srcDatasetId &&
+ values.destDatasetName &&
+ values.destDatasetType
+ );
+ }
+ case 2:
+ return selectedOperators.length > 0;
+ default:
+ return false;
+ }
+ };
+
+ const renderStepContent = () => {
+ switch (currentStep) {
+ case 1:
+ return (
+
+ );
+ case 2:
+ return renderStepTwo;
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+ {/* Step Content */}
+
+
{renderStepContent()}
+
+ navigate("/data/cleansing")}>取消
+ {currentStep > 1 && 上一步}
+ {currentStep === 2 ? (
+ }
+ onClick={handleSave}
+ disabled={!canProceed()}
+ >
+ 创建任务
+
+ ) : (
+
+ 下一步
+
+ )}
+
+
+
+ );
+}
diff --git a/frontend/src/pages/DataCleansing/Create/CreateTemplate.tsx b/frontend/src/pages/DataCleansing/Create/CreateTemplate.tsx
index 98c491d0e..ca8694bcc 100644
--- a/frontend/src/pages/DataCleansing/Create/CreateTemplate.tsx
+++ b/frontend/src/pages/DataCleansing/Create/CreateTemplate.tsx
@@ -1,142 +1,142 @@
-import {useEffect, useState} from "react";
-import {Button, Steps, Form, message} from "antd";
-import {Link, useNavigate, useParams} from "react-router";
-
-import { ArrowLeft } from "lucide-react";
-import {
- createCleaningTemplateUsingPost,
- queryCleaningTemplateByIdUsingGet,
- updateCleaningTemplateByIdUsingPut
-} from "../cleansing.api";
-import CleansingTemplateStepOne from "./components/CreateTemplateStepOne";
-import { useCreateStepTwo } from "./hooks/useCreateStepTwo";
-
-export default function CleansingTemplateCreate() {
- const { id = "" } = useParams()
- const navigate = useNavigate();
- const [form] = Form.useForm();
- const [templateConfig, setTemplateConfig] = useState({
- name: "",
- description: "",
- });
-
- const fetchTemplateDetail = async () => {
- if (!id) return;
- try {
- const { data } = await queryCleaningTemplateByIdUsingGet(id);
- setTemplateConfig(data);
- } catch (error) {
- message.error("获取任务详情失败");
- navigate("/data/cleansing");
- }
- };
-
- useEffect(() => {
- fetchTemplateDetail()
- }, [id]);
-
- const handleSave = async () => {
- const template = {
- ...templateConfig,
- instance: selectedOperators.map((item) => ({
- id: item.id,
- overrides: {
- ...item.defaultParams,
- ...item.overrides,
- },
- inputs: item.inputs,
- outputs: item.outputs,
- })),
- };
-
- !id && await createCleaningTemplateUsingPost(template) && message.success("模板创建成功");
- id && await updateCleaningTemplateByIdUsingPut(id, template) && message.success("模板更新成功");
- navigate("/data/cleansing?view=template");
- };
-
- const {
- renderStepTwo,
- selectedOperators,
- currentStep,
- handlePrev,
- handleNext,
- } = useCreateStepTwo();
-
- const canProceed = () => {
- const values = form.getFieldsValue();
-
- switch (currentStep) {
- case 1:
- return values.name;
- case 2:
- return selectedOperators.length > 0;
- default:
- return false;
- }
- };
-
- const renderStepContent = () => {
- switch (currentStep) {
- case 1:
- return (
-
- );
- case 2:
- return renderStepTwo;
- default:
- return null;
- }
- };
-
- return (
-
- {/* Header */}
-
-
-
-
-
-
-
-
{id ? '更新清洗模板' : '创建清洗模板'}
-
-
-
-
-
-
-
-
{renderStepContent()}
-
- navigate("/data/cleansing")}>取消
- {currentStep > 1 && 上一步}
- {currentStep === 2 ? (
-
- {id ? '更新模板' : '创建模板'}
-
- ) : (
-
- 下一步
-
- )}
-
-
-
- );
-}
+import {useEffect, useState} from "react";
+import {Button, Steps, Form, message} from "antd";
+import {Link, useNavigate, useParams} from "react-router";
+
+import { ArrowLeft } from "lucide-react";
+import {
+ createCleaningTemplateUsingPost,
+ queryCleaningTemplateByIdUsingGet,
+ updateCleaningTemplateByIdUsingPut
+} from "../cleansing.api";
+import CleansingTemplateStepOne from "./components/CreateTemplateStepOne";
+import { useCreateStepTwo } from "./hooks/useCreateStepTwo";
+
+export default function CleansingTemplateCreate() {
+ const { id = "" } = useParams()
+ const navigate = useNavigate();
+ const [form] = Form.useForm();
+ const [templateConfig, setTemplateConfig] = useState({
+ name: "",
+ description: "",
+ });
+
+ const fetchTemplateDetail = async () => {
+ if (!id) return;
+ try {
+ const { data } = await queryCleaningTemplateByIdUsingGet(id);
+ setTemplateConfig(data);
+ } catch (error) {
+ message.error("获取任务详情失败");
+ navigate("/data/cleansing");
+ }
+ };
+
+ useEffect(() => {
+ fetchTemplateDetail()
+ }, [id]);
+
+ const handleSave = async () => {
+ const template = {
+ ...templateConfig,
+ instance: selectedOperators.map((item) => ({
+ id: item.id,
+ overrides: {
+ ...item.defaultParams,
+ ...item.overrides,
+ },
+ inputs: item.inputs,
+ outputs: item.outputs,
+ })),
+ };
+
+ !id && await createCleaningTemplateUsingPost(template) && message.success("模板创建成功");
+ id && await updateCleaningTemplateByIdUsingPut(id, template) && message.success("模板更新成功");
+ navigate("/data/cleansing?view=template");
+ };
+
+ const {
+ renderStepTwo,
+ selectedOperators,
+ currentStep,
+ handlePrev,
+ handleNext,
+ } = useCreateStepTwo();
+
+ const canProceed = () => {
+ const values = form.getFieldsValue();
+
+ switch (currentStep) {
+ case 1:
+ return values.name;
+ case 2:
+ return selectedOperators.length > 0;
+ default:
+ return false;
+ }
+ };
+
+ const renderStepContent = () => {
+ switch (currentStep) {
+ case 1:
+ return (
+
+ );
+ case 2:
+ return renderStepTwo;
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+
+
+
{id ? '更新清洗模板' : '创建清洗模板'}
+
+
+
+
+
+
+
+
{renderStepContent()}
+
+ navigate("/data/cleansing")}>取消
+ {currentStep > 1 && 上一步}
+ {currentStep === 2 ? (
+
+ {id ? '更新模板' : '创建模板'}
+
+ ) : (
+
+ 下一步
+
+ )}
+
+
+
+ );
+}
diff --git a/frontend/src/pages/DataCleansing/Create/components/CreateTaskStepOne.tsx b/frontend/src/pages/DataCleansing/Create/components/CreateTaskStepOne.tsx
index 9a471a3c0..7fa6595e0 100644
--- a/frontend/src/pages/DataCleansing/Create/components/CreateTaskStepOne.tsx
+++ b/frontend/src/pages/DataCleansing/Create/components/CreateTaskStepOne.tsx
@@ -1,113 +1,113 @@
-import RadioCard from "@/components/RadioCard";
-import { queryDatasetsUsingGet } from "@/pages/DataManagement/dataset.api";
-import { datasetTypes, mapDataset } from "@/pages/DataManagement/dataset.const";
-import {
- Dataset,
- DatasetSubType,
- DatasetType,
-} from "@/pages/DataManagement/dataset.model";
-import { Input, Select, Form } from "antd";
-import TextArea from "antd/es/input/TextArea";
-import { useEffect, useState } from "react";
-
-export default function CreateTaskStepOne({
- form,
- taskConfig,
- setTaskConfig,
-}: {
- form: any;
- taskConfig: {
- name: string;
- description: string;
- datasetId: string;
- destDatasetName: string;
- type: DatasetType;
- destDatasetType: DatasetSubType;
- };
- setTaskConfig: (config: any) => void;
-}) {
- const [datasets, setDatasets] = useState([]);
-
- const fetchDatasets = async () => {
- const { data } = await queryDatasetsUsingGet({ page: 1, size: 1000 });
- setDatasets(data.content.map(mapDataset) || []);
- };
-
- useEffect(() => {
- fetchDatasets();
- }, []);
-
- const handleValuesChange = (currentValue, allValues) => {
- const [key, value] = Object.entries(currentValue)[0];
- let dataset = null;
- if (key === "srcDatasetId") {
- dataset = datasets.find((d) => d.id === value);
- setTaskConfig({
- ...taskConfig,
- ...allValues,
- srcDatasetName: dataset?.name || "",
- });
- } else {
- setTaskConfig({ ...taskConfig, ...allValues });
- }
- };
-
- return (
-
-
-
-
-
-
-
- 数据源选择
-
-
-
-
-
-
-
- {
- form.setFieldValue("destDatasetType", type);
- setTaskConfig({
- ...taskConfig,
- destDatasetType: type as DatasetSubType,
- });
- }}
- />
-
-
- );
-}
+import RadioCard from "@/components/RadioCard";
+import { queryDatasetsUsingGet } from "@/pages/DataManagement/dataset.api";
+import { datasetTypes, mapDataset } from "@/pages/DataManagement/dataset.const";
+import {
+ Dataset,
+ DatasetSubType,
+ DatasetType,
+} from "@/pages/DataManagement/dataset.model";
+import { Input, Select, Form } from "antd";
+import TextArea from "antd/es/input/TextArea";
+import { useEffect, useState } from "react";
+
+export default function CreateTaskStepOne({
+ form,
+ taskConfig,
+ setTaskConfig,
+}: {
+ form: any;
+ taskConfig: {
+ name: string;
+ description: string;
+ datasetId: string;
+ destDatasetName: string;
+ type: DatasetType;
+ destDatasetType: DatasetSubType;
+ };
+ setTaskConfig: (config: any) => void;
+}) {
+ const [datasets, setDatasets] = useState([]);
+
+ const fetchDatasets = async () => {
+ const { data } = await queryDatasetsUsingGet({ page: 1, size: 1000 });
+ setDatasets(data.content.map(mapDataset) || []);
+ };
+
+ useEffect(() => {
+ fetchDatasets();
+ }, []);
+
+ const handleValuesChange = (currentValue, allValues) => {
+ const [key, value] = Object.entries(currentValue)[0];
+ let dataset = null;
+ if (key === "srcDatasetId") {
+ dataset = datasets.find((d) => d.id === value);
+ setTaskConfig({
+ ...taskConfig,
+ ...allValues,
+ srcDatasetName: dataset?.name || "",
+ });
+ } else {
+ setTaskConfig({ ...taskConfig, ...allValues });
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+ 数据源选择
+
+
+
+
+
+
+
+ {
+ form.setFieldValue("destDatasetType", type);
+ setTaskConfig({
+ ...taskConfig,
+ destDatasetType: type as DatasetSubType,
+ });
+ }}
+ />
+
+
+ );
+}
diff --git a/frontend/src/pages/DataCleansing/Create/components/CreateTemplateStepOne.tsx b/frontend/src/pages/DataCleansing/Create/components/CreateTemplateStepOne.tsx
index 666df8a3f..0587e4c9d 100644
--- a/frontend/src/pages/DataCleansing/Create/components/CreateTemplateStepOne.tsx
+++ b/frontend/src/pages/DataCleansing/Create/components/CreateTemplateStepOne.tsx
@@ -1,44 +1,44 @@
-import { Input, Form } from "antd";
-import {useEffect} from "react";
-
-const { TextArea } = Input;
-
-export default function CreateTemplateStepOne({
- form,
- templateConfig,
- setTemplateConfig,
-}: {
- form: any;
- templateConfig: { name: string; description: string; type: string };
- setTemplateConfig: React.Dispatch<
- React.SetStateAction<{ name: string; description: string; type: string }>
- >;
-}) {
- const handleValuesChange = (_, allValues) => {
- setTemplateConfig({ ...templateConfig, ...allValues });
- };
-
- useEffect(() => {
- form.setFieldsValue(templateConfig);
- }, [templateConfig]);
-
- return (
-
-
-
-
-
-
-
- );
-}
+import { Input, Form } from "antd";
+import {useEffect} from "react";
+
+const { TextArea } = Input;
+
+export default function CreateTemplateStepOne({
+ form,
+ templateConfig,
+ setTemplateConfig,
+}: {
+ form: any;
+ templateConfig: { name: string; description: string; type: string };
+ setTemplateConfig: React.Dispatch<
+ React.SetStateAction<{ name: string; description: string; type: string }>
+ >;
+}) {
+ const handleValuesChange = (_, allValues) => {
+ setTemplateConfig({ ...templateConfig, ...allValues });
+ };
+
+ useEffect(() => {
+ form.setFieldsValue(templateConfig);
+ }, [templateConfig]);
+
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/DataCleansing/Create/components/OperatorConfig.tsx b/frontend/src/pages/DataCleansing/Create/components/OperatorConfig.tsx
index b304ed0ae..02bf7e105 100644
--- a/frontend/src/pages/DataCleansing/Create/components/OperatorConfig.tsx
+++ b/frontend/src/pages/DataCleansing/Create/components/OperatorConfig.tsx
@@ -1,81 +1,81 @@
-import React from "react";
-import { Tag, Divider, Form } from "antd";
-import ParamConfig from "./ParamConfig";
-import { Settings } from "lucide-react";
-import { OperatorI } from "@/pages/OperatorMarket/operator.model";
-
-// OperatorConfig/OperatorTemplate 类型需根据主文件实际导入
-interface OperatorConfigProps {
- selectedOp: OperatorI;
- renderParamConfig?: (
- operator: OperatorI,
- paramKey: string,
- param: any
- ) => React.ReactNode;
- handleConfigChange?: (
- operatorId: string,
- paramKey: string,
- value: any
- ) => void;
-}
-
-const OperatorConfig: React.FC = ({
- selectedOp,
- renderParamConfig,
- handleConfigChange,
-}) => {
- return (
-
-
-
-
- 参数配置
-
-
-
- {selectedOp ? (
-
-
-
- {selectedOp.name}
-
-
- {selectedOp.description}
-
-
- {selectedOp?.tags?.map((tag: string) => (
-
- {tag}
-
- ))}
-
-
-
-
-
- ) : (
-
- )}
-
-
- );
-};
-
-export default OperatorConfig;
+import React from "react";
+import { Tag, Divider, Form } from "antd";
+import ParamConfig from "./ParamConfig";
+import { Settings } from "lucide-react";
+import { OperatorI } from "@/pages/OperatorMarket/operator.model";
+
+// OperatorConfig/OperatorTemplate 类型需根据主文件实际导入
+interface OperatorConfigProps {
+ selectedOp: OperatorI;
+ renderParamConfig?: (
+ operator: OperatorI,
+ paramKey: string,
+ param: any
+ ) => React.ReactNode;
+ handleConfigChange?: (
+ operatorId: string,
+ paramKey: string,
+ value: any
+ ) => void;
+}
+
+const OperatorConfig: React.FC = ({
+ selectedOp,
+ renderParamConfig,
+ handleConfigChange,
+}) => {
+ return (
+
+
+
+
+ 参数配置
+
+
+
+ {selectedOp ? (
+
+
+
+ {selectedOp.name}
+
+
+ {selectedOp.description}
+
+
+ {selectedOp?.tags?.map((tag: string) => (
+
+ {tag}
+
+ ))}
+
+
+
+
+
+ ) : (
+
+ )}
+
+
+ );
+};
+
+export default OperatorConfig;
diff --git a/frontend/src/pages/DataCleansing/Create/components/OperatorLibrary.tsx b/frontend/src/pages/DataCleansing/Create/components/OperatorLibrary.tsx
index cb7bb5f54..0792e9a8a 100644
--- a/frontend/src/pages/DataCleansing/Create/components/OperatorLibrary.tsx
+++ b/frontend/src/pages/DataCleansing/Create/components/OperatorLibrary.tsx
@@ -1,287 +1,287 @@
-import React, {useEffect, useMemo, useState} from "react";
-import {Button, Card, Checkbox, Collapse, Input, Select, Tag, Tooltip,} from "antd";
-import {SearchOutlined, StarFilled, StarOutlined} from "@ant-design/icons";
-import {CategoryI, OperatorI} from "@/pages/OperatorMarket/operator.model";
-import {Layers} from "lucide-react";
-import {updateOperatorByIdUsingPut} from "@/pages/OperatorMarket/operator.api.ts";
-
-interface OperatorListProps {
- operators: OperatorI[];
- favorites: Set;
- showPoppular?: boolean;
- toggleFavorite: (id: string) => void;
- toggleOperator: (operator: OperatorI) => void;
- selectedOperators: OperatorI[];
- onDragOperator: (
- e: React.DragEvent,
- item: OperatorI,
- source: "library"
- ) => void;
-}
-
-const handleStar = async (operator: OperatorI, toggleFavorite: (id: string) => void) => {
- const data = {
- id: operator.id,
- isStar: !operator.isStar
- };
- await updateOperatorByIdUsingPut(operator.id, data);
- toggleFavorite(operator.id)
-}
-
-const OperatorList: React.FC = ({
- operators,
- favorites,
- toggleFavorite,
- toggleOperator,
- selectedOperators,
- onDragOperator,
-}) => (
-
- {operators.map((operator) => {
- // 判断是否已选
- const isSelected = selectedOperators.some((op) => op.id === operator.id);
- return (
-
onDragOperator(e, operator, "library")}
- onClick={() => toggleOperator(operator)}
- >
-
-
-
-
- {operator.name}
-
-
-
{
- event.stopPropagation();
- handleStar(operator, toggleFavorite);
- }}
- >
- {favorites.has(operator.id) ? (
-
- ) : (
-
- )}
-
-
-
- );
- })}
-
-);
-
-interface OperatorLibraryProps {
- selectedOperators: OperatorI[];
- operatorList: OperatorI[];
- categoryOptions: CategoryI[];
- setSelectedOperators: (operators: OperatorI[]) => void;
- toggleOperator: (template: OperatorI) => void;
- handleDragStart: (
- e: React.DragEvent,
- item: OperatorI,
- source: "library"
- ) => void;
-}
-
-const OperatorLibrary: React.FC = ({
- selectedOperators,
- operatorList,
- categoryOptions,
- setSelectedOperators,
- toggleOperator,
- handleDragStart,
-}) => {
- const [searchTerm, setSearchTerm] = useState("");
- const [showFavorites, setShowFavorites] = useState(false);
- const [favorites, setFavorites] = useState>(new Set());
- const [selectedCategory, setSelectedCategory] = useState("all");
- const [expandedCategories, setExpandedCategories] = useState>(
- new Set([])
- );
-
- // 按分类分组
- const groupedOperators = useMemo(() => {
- const groups: { [key: string]: OperatorI[] } = {};
- categoryOptions.forEach((cat: any) => {
- groups[cat.name] = {
- ...cat,
- operators: operatorList.filter((op) => op.categories?.includes(cat.id)),
- };
- });
-
- if (selectedCategory && selectedCategory !== "all") {
- Object.keys(groups).forEach((key) => {
- if (groups[key].id !== selectedCategory) {
- delete groups[key];
- }
- });
- }
-
- if (searchTerm) {
- Object.keys(groups).forEach((key) => {
- groups[key].operators = groups[key].operators.filter((operator) =>
- operator.name.toLowerCase().includes(searchTerm.toLowerCase())
- );
- if (groups[key].operators.length === 0) {
- delete groups[key];
- }
- });
- }
-
- if (showFavorites) {
- Object.keys(groups).forEach((key) => {
- groups[key].operators = groups[key].operators.filter((operator) =>
- favorites.has(operator.id)
- );
- if (groups[key].operators.length === 0) {
- delete groups[key];
- }
- });
- }
-
- setExpandedCategories(new Set(Object.keys(groups)));
- return groups;
- }, [categoryOptions, selectedCategory, searchTerm, showFavorites]);
-
- // 过滤算子
- const filteredOperators = useMemo(() => {
- return Object.values(groupedOperators).flatMap(
- (category) => category.operators
- );
- }, [groupedOperators]);
-
- // 收藏切换
- const toggleFavorite = (operatorId: string) => {
- const newFavorites = new Set(favorites);
- if (newFavorites.has(operatorId)) {
- newFavorites.delete(operatorId);
- } else {
- newFavorites.add(operatorId);
- }
- setFavorites(newFavorites);
- };
-
- const fetchFavorite = async () => {
- const newFavorites = new Set(favorites);
- operatorList.forEach(item => {
- item.isStar && newFavorites.add(item.id);
- });
- setFavorites(newFavorites);
- }
-
- useEffect(() => {
- fetchFavorite()
- }, [operatorList]);
-
- // 全选分类算子
- const handleSelectAll = (operators: OperatorI[]) => {
- const newSelected = [...selectedOperators];
- operators.forEach((operator) => {
- if (!newSelected.some((op) => op.id === operator.id)) {
- newSelected.push(operator);
- }
- });
- setSelectedOperators(newSelected);
- };
-
- return (
-
-
-
-
- 算子库({filteredOperators.length})
-
-
-
- {/* 过滤器 */}
-
- }
- placeholder="搜索算子名称..."
- value={searchTerm}
- allowClear
- onChange={(e) => setSearchTerm(e.target.value)}
- />
-
-
- setShowFavorites(!showFavorites)}
- >
- {showFavorites ? (
-
- ) : (
-
- )}
-
-
-
- {/* 算子列表 */}
-
- {/* 分类算子 */}
-
- setExpandedCategories(
- new Set(Array.isArray(keys) ? keys : [keys])
- )
- }
- >
- {Object.entries(groupedOperators).map(([key, category]) => (
-
-
- {category.name}
- {category.operators.length}
-
- {
- e.stopPropagation();
- handleSelectAll(category.operators);
- }}
- >
- 全选
-
-
- }
- >
-
-
- ))}
-
- {filteredOperators.length === 0 && (
-
- )}
-
-
-
- );
-};
-export default OperatorLibrary;
+import React, {useEffect, useMemo, useState} from "react";
+import {Button, Card, Checkbox, Collapse, Input, Select, Tag, Tooltip,} from "antd";
+import {SearchOutlined, StarFilled, StarOutlined} from "@ant-design/icons";
+import {CategoryI, OperatorI} from "@/pages/OperatorMarket/operator.model";
+import {Layers} from "lucide-react";
+import {updateOperatorByIdUsingPut} from "@/pages/OperatorMarket/operator.api.ts";
+
+interface OperatorListProps {
+ operators: OperatorI[];
+ favorites: Set;
+ showPoppular?: boolean;
+ toggleFavorite: (id: string) => void;
+ toggleOperator: (operator: OperatorI) => void;
+ selectedOperators: OperatorI[];
+ onDragOperator: (
+ e: React.DragEvent,
+ item: OperatorI,
+ source: "library"
+ ) => void;
+}
+
+const handleStar = async (operator: OperatorI, toggleFavorite: (id: string) => void) => {
+ const data = {
+ id: operator.id,
+ isStar: !operator.isStar
+ };
+ await updateOperatorByIdUsingPut(operator.id, data);
+ toggleFavorite(operator.id)
+}
+
+const OperatorList: React.FC = ({
+ operators,
+ favorites,
+ toggleFavorite,
+ toggleOperator,
+ selectedOperators,
+ onDragOperator,
+}) => (
+
+ {operators.map((operator) => {
+ // 判断是否已选
+ const isSelected = selectedOperators.some((op) => op.id === operator.id);
+ return (
+
onDragOperator(e, operator, "library")}
+ onClick={() => toggleOperator(operator)}
+ >
+
+
+
+
+ {operator.name}
+
+
+
{
+ event.stopPropagation();
+ handleStar(operator, toggleFavorite);
+ }}
+ >
+ {favorites.has(operator.id) ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+ })}
+
+);
+
+interface OperatorLibraryProps {
+ selectedOperators: OperatorI[];
+ operatorList: OperatorI[];
+ categoryOptions: CategoryI[];
+ setSelectedOperators: (operators: OperatorI[]) => void;
+ toggleOperator: (template: OperatorI) => void;
+ handleDragStart: (
+ e: React.DragEvent,
+ item: OperatorI,
+ source: "library"
+ ) => void;
+}
+
+const OperatorLibrary: React.FC = ({
+ selectedOperators,
+ operatorList,
+ categoryOptions,
+ setSelectedOperators,
+ toggleOperator,
+ handleDragStart,
+}) => {
+ const [searchTerm, setSearchTerm] = useState("");
+ const [showFavorites, setShowFavorites] = useState(false);
+ const [favorites, setFavorites] = useState>(new Set());
+ const [selectedCategory, setSelectedCategory] = useState("all");
+ const [expandedCategories, setExpandedCategories] = useState>(
+ new Set([])
+ );
+
+ // 按分类分组
+ const groupedOperators = useMemo(() => {
+ const groups: { [key: string]: OperatorI[] } = {};
+ categoryOptions.forEach((cat: any) => {
+ groups[cat.name] = {
+ ...cat,
+ operators: operatorList.filter((op) => op.categories?.includes(cat.id)),
+ };
+ });
+
+ if (selectedCategory && selectedCategory !== "all") {
+ Object.keys(groups).forEach((key) => {
+ if (groups[key].id !== selectedCategory) {
+ delete groups[key];
+ }
+ });
+ }
+
+ if (searchTerm) {
+ Object.keys(groups).forEach((key) => {
+ groups[key].operators = groups[key].operators.filter((operator) =>
+ operator.name.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+ if (groups[key].operators.length === 0) {
+ delete groups[key];
+ }
+ });
+ }
+
+ if (showFavorites) {
+ Object.keys(groups).forEach((key) => {
+ groups[key].operators = groups[key].operators.filter((operator) =>
+ favorites.has(operator.id)
+ );
+ if (groups[key].operators.length === 0) {
+ delete groups[key];
+ }
+ });
+ }
+
+ setExpandedCategories(new Set(Object.keys(groups)));
+ return groups;
+ }, [categoryOptions, selectedCategory, searchTerm, showFavorites]);
+
+ // 过滤算子
+ const filteredOperators = useMemo(() => {
+ return Object.values(groupedOperators).flatMap(
+ (category) => category.operators
+ );
+ }, [groupedOperators]);
+
+ // 收藏切换
+ const toggleFavorite = (operatorId: string) => {
+ const newFavorites = new Set(favorites);
+ if (newFavorites.has(operatorId)) {
+ newFavorites.delete(operatorId);
+ } else {
+ newFavorites.add(operatorId);
+ }
+ setFavorites(newFavorites);
+ };
+
+ const fetchFavorite = async () => {
+ const newFavorites = new Set(favorites);
+ operatorList.forEach(item => {
+ item.isStar && newFavorites.add(item.id);
+ });
+ setFavorites(newFavorites);
+ }
+
+ useEffect(() => {
+ fetchFavorite()
+ }, [operatorList]);
+
+ // 全选分类算子
+ const handleSelectAll = (operators: OperatorI[]) => {
+ const newSelected = [...selectedOperators];
+ operators.forEach((operator) => {
+ if (!newSelected.some((op) => op.id === operator.id)) {
+ newSelected.push(operator);
+ }
+ });
+ setSelectedOperators(newSelected);
+ };
+
+ return (
+
+
+
+
+ 算子库({filteredOperators.length})
+
+
+
+ {/* 过滤器 */}
+
+ }
+ placeholder="搜索算子名称..."
+ value={searchTerm}
+ allowClear
+ onChange={(e) => setSearchTerm(e.target.value)}
+ />
+
+
+ setShowFavorites(!showFavorites)}
+ >
+ {showFavorites ? (
+
+ ) : (
+
+ )}
+
+
+
+ {/* 算子列表 */}
+
+ {/* 分类算子 */}
+
+ setExpandedCategories(
+ new Set(Array.isArray(keys) ? keys : [keys])
+ )
+ }
+ >
+ {Object.entries(groupedOperators).map(([key, category]) => (
+
+
+ {category.name}
+ {category.operators.length}
+
+ {
+ e.stopPropagation();
+ handleSelectAll(category.operators);
+ }}
+ >
+ 全选
+
+
+ }
+ >
+
+
+ ))}
+
+ {filteredOperators.length === 0 && (
+
+ )}
+
+
+
+ );
+};
+export default OperatorLibrary;
diff --git a/frontend/src/pages/DataCleansing/Create/components/OperatorOrchestration.tsx b/frontend/src/pages/DataCleansing/Create/components/OperatorOrchestration.tsx
index 7799b6a97..75ec56e4c 100644
--- a/frontend/src/pages/DataCleansing/Create/components/OperatorOrchestration.tsx
+++ b/frontend/src/pages/DataCleansing/Create/components/OperatorOrchestration.tsx
@@ -1,213 +1,213 @@
-import React, {useMemo, useState} from "react";
-import { Card, Input, Tag, Select, Button } from "antd";
-import { DeleteOutlined } from "@ant-design/icons";
-import { CleansingTemplate } from "../../cleansing.model";
-import { Workflow } from "lucide-react";
-import {CategoryI, OperatorI} from "@/pages/OperatorMarket/operator.model";
-
-interface OperatorFlowProps {
- selectedOperators: OperatorI[];
- configOperator: OperatorI | null;
- templates: CleansingTemplate[];
- currentTemplate: CleansingTemplate | null;
- categoryOptions: [];
- setCurrentTemplate: (template: CleansingTemplate | null) => void;
- removeOperator: (id: string) => void;
- setSelectedOperators: (operators: OperatorI[]) => void;
- setConfigOperator: (operator: OperatorI | null) => void;
- handleDragStart: (
- e: React.DragEvent,
- operator: OperatorI,
- source: "sort"
- ) => void;
- handleItemDragOver: (e: React.DragEvent, itemId: string) => void;
- handleItemDragLeave: (e: React.DragEvent) => void;
- handleItemDrop: (e: React.DragEvent, index: number) => void;
- handleContainerDragOver: (e: React.DragEvent) => void;
- handleContainerDragLeave: (e: React.DragEvent) => void;
- handleDragEnd: (e: React.DragEvent) => void;
- handleDropToContainer: (e: React.DragEvent) => void;
-}
-
-const OperatorFlow: React.FC = ({
- selectedOperators,
- configOperator,
- templates,
- currentTemplate,
- categoryOptions,
- setSelectedOperators,
- setConfigOperator,
- removeOperator,
- setCurrentTemplate,
- handleDragStart,
- handleItemDragLeave,
- handleItemDragOver,
- handleItemDrop,
- handleContainerDragLeave,
- handleDropToContainer,
- handleDragEnd,
-}) => {
- const [editingIndex, setEditingIndex] = useState(null);
-
- const categoryMap = useMemo(() => {
- const map: { [key: string]: CategoryI } = {};
- categoryOptions.forEach((cat: any) => {
- map[cat.id] = {
- ...cat,
- };
- });
- return map;
- }, [categoryOptions]);
-
- // 添加编号修改处理函数
- const handleIndexChange = (operatorId: string, newIndex: string) => {
- const index = Number.parseInt(newIndex);
- if (isNaN(index) || index < 1 || index > selectedOperators.length) {
- return; // 无效输入,不处理
- }
-
- const currentIndex = selectedOperators.findIndex(
- (op) => op.id === operatorId
- );
- if (currentIndex === -1) return;
-
- const targetIndex = index - 1; // 转换为0基索引
- if (currentIndex === targetIndex) return; // 位置没有变化
-
- const newOperators = [...selectedOperators];
- const [movedOperator] = newOperators.splice(currentIndex, 1);
- newOperators.splice(targetIndex, 0, movedOperator);
-
- setSelectedOperators(newOperators);
- setEditingIndex(null);
- };
-
- return (
-
- {/* 工具栏 */}
-
-
-
-
- 算子编排({selectedOperators.length}){" "}
- {
- setConfigOperator(null);
- setSelectedOperators([]);
- }}
- disabled={selectedOperators.length === 0}
- >
- 清空
-
-
-
-
-
- {/* 编排区域 */}
-
e.preventDefault()}
- onDragLeave={handleContainerDragLeave}
- onDrop={handleDropToContainer}
- >
- {selectedOperators.map((operator, index) => (
-
handleDragStart(e, operator, "sort")}
- onDragEnd={handleDragEnd}
- onDragOver={(e) => handleItemDragOver(e, operator.id)}
- onDragLeave={handleItemDragLeave}
- onDrop={(e) => handleItemDrop(e, index)}
- onClick={() => setConfigOperator(operator)}
- >
-
- {/* 可编辑编号 */}
-
⋮⋮
- {editingIndex === operator.id ? (
-
handleIndexChange(operator.id, e.target.value)}
- onKeyDown={(e) => {
- if (e.key === "Enter")
- handleIndexChange(
- operator.id,
- (e.target as HTMLInputElement).value
- );
- else if (e.key === "Escape") setEditingIndex(null);
- }}
- onClick={(e) => e.stopPropagation()}
- />
- ) : (
-
{
- e.stopPropagation();
- setEditingIndex(operator.id);
- }}
- >
- {index + 1}
-
- )}
- {/* 算子图标和名称 */}
-
-
- {operator.name}
-
-
- {operator?.categories?.map((categoryId) => {
- return
{categoryMap[categoryId].name}
- })}
- {/* 操作按钮 */}
-
{
- e.stopPropagation();
- removeOperator(operator.id);
- }}
- >
-
-
-
-
- ))}
- {selectedOperators.length === 0 && (
-
-
-
开始构建您的算子流程
-
- 从左侧算子库拖拽算子到此处,或点击算子添加
-
-
- )}
-
-
- );
-};
-
-export default OperatorFlow;
+import React, {useMemo, useState} from "react";
+import { Card, Input, Tag, Select, Button } from "antd";
+import { DeleteOutlined } from "@ant-design/icons";
+import { CleansingTemplate } from "../../cleansing.model";
+import { Workflow } from "lucide-react";
+import {CategoryI, OperatorI} from "@/pages/OperatorMarket/operator.model";
+
+interface OperatorFlowProps {
+ selectedOperators: OperatorI[];
+ configOperator: OperatorI | null;
+ templates: CleansingTemplate[];
+ currentTemplate: CleansingTemplate | null;
+ categoryOptions: [];
+ setCurrentTemplate: (template: CleansingTemplate | null) => void;
+ removeOperator: (id: string) => void;
+ setSelectedOperators: (operators: OperatorI[]) => void;
+ setConfigOperator: (operator: OperatorI | null) => void;
+ handleDragStart: (
+ e: React.DragEvent,
+ operator: OperatorI,
+ source: "sort"
+ ) => void;
+ handleItemDragOver: (e: React.DragEvent, itemId: string) => void;
+ handleItemDragLeave: (e: React.DragEvent) => void;
+ handleItemDrop: (e: React.DragEvent, index: number) => void;
+ handleContainerDragOver: (e: React.DragEvent) => void;
+ handleContainerDragLeave: (e: React.DragEvent) => void;
+ handleDragEnd: (e: React.DragEvent) => void;
+ handleDropToContainer: (e: React.DragEvent) => void;
+}
+
+const OperatorFlow: React.FC = ({
+ selectedOperators,
+ configOperator,
+ templates,
+ currentTemplate,
+ categoryOptions,
+ setSelectedOperators,
+ setConfigOperator,
+ removeOperator,
+ setCurrentTemplate,
+ handleDragStart,
+ handleItemDragLeave,
+ handleItemDragOver,
+ handleItemDrop,
+ handleContainerDragLeave,
+ handleDropToContainer,
+ handleDragEnd,
+}) => {
+ const [editingIndex, setEditingIndex] = useState(null);
+
+ const categoryMap = useMemo(() => {
+ const map: { [key: string]: CategoryI } = {};
+ categoryOptions.forEach((cat: any) => {
+ map[cat.id] = {
+ ...cat,
+ };
+ });
+ return map;
+ }, [categoryOptions]);
+
+ // 添加编号修改处理函数
+ const handleIndexChange = (operatorId: string, newIndex: string) => {
+ const index = Number.parseInt(newIndex);
+ if (isNaN(index) || index < 1 || index > selectedOperators.length) {
+ return; // 无效输入,不处理
+ }
+
+ const currentIndex = selectedOperators.findIndex(
+ (op) => op.id === operatorId
+ );
+ if (currentIndex === -1) return;
+
+ const targetIndex = index - 1; // 转换为0基索引
+ if (currentIndex === targetIndex) return; // 位置没有变化
+
+ const newOperators = [...selectedOperators];
+ const [movedOperator] = newOperators.splice(currentIndex, 1);
+ newOperators.splice(targetIndex, 0, movedOperator);
+
+ setSelectedOperators(newOperators);
+ setEditingIndex(null);
+ };
+
+ return (
+
+ {/* 工具栏 */}
+
+
+
+
+ 算子编排({selectedOperators.length}){" "}
+ {
+ setConfigOperator(null);
+ setSelectedOperators([]);
+ }}
+ disabled={selectedOperators.length === 0}
+ >
+ 清空
+
+
+
+
+
+ {/* 编排区域 */}
+
e.preventDefault()}
+ onDragLeave={handleContainerDragLeave}
+ onDrop={handleDropToContainer}
+ >
+ {selectedOperators.map((operator, index) => (
+
handleDragStart(e, operator, "sort")}
+ onDragEnd={handleDragEnd}
+ onDragOver={(e) => handleItemDragOver(e, operator.id)}
+ onDragLeave={handleItemDragLeave}
+ onDrop={(e) => handleItemDrop(e, index)}
+ onClick={() => setConfigOperator(operator)}
+ >
+
+ {/* 可编辑编号 */}
+
⋮⋮
+ {editingIndex === operator.id ? (
+
handleIndexChange(operator.id, e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter")
+ handleIndexChange(
+ operator.id,
+ (e.target as HTMLInputElement).value
+ );
+ else if (e.key === "Escape") setEditingIndex(null);
+ }}
+ onClick={(e) => e.stopPropagation()}
+ />
+ ) : (
+
{
+ e.stopPropagation();
+ setEditingIndex(operator.id);
+ }}
+ >
+ {index + 1}
+
+ )}
+ {/* 算子图标和名称 */}
+
+
+ {operator.name}
+
+
+ {operator?.categories?.map((categoryId) => {
+ return
{categoryMap[categoryId].name}
+ })}
+ {/* 操作按钮 */}
+
{
+ e.stopPropagation();
+ removeOperator(operator.id);
+ }}
+ >
+
+
+
+
+ ))}
+ {selectedOperators.length === 0 && (
+
+
+
开始构建您的算子流程
+
+ 从左侧算子库拖拽算子到此处,或点击算子添加
+
+
+ )}
+
+
+ );
+};
+
+export default OperatorFlow;
diff --git a/frontend/src/pages/DataCleansing/Create/components/ParamConfig.tsx b/frontend/src/pages/DataCleansing/Create/components/ParamConfig.tsx
index 417c0a27f..dc8a79db0 100644
--- a/frontend/src/pages/DataCleansing/Create/components/ParamConfig.tsx
+++ b/frontend/src/pages/DataCleansing/Create/components/ParamConfig.tsx
@@ -1,245 +1,245 @@
-import React from "react";
-import {
- Input,
- Select,
- Radio,
- Checkbox,
- Form,
- InputNumber,
- Slider,
- Space,
-} from "antd";
-import { ConfigI, OperatorI } from "@/pages/OperatorMarket/operator.model";
-
-interface ParamConfigProps {
- operator: OperatorI;
- paramKey: string;
- param: ConfigI;
- onParamChange?: (operatorId: string, paramKey: string, value: any) => void;
-}
-
-const ParamConfig: React.FC = ({
- operator,
- paramKey,
- param,
- onParamChange,
-}) => {
- if (!param) return null;
- let defaultVal: any = param.defaultVal;
- if (param.type === "range") {
-
- defaultVal = Array.isArray(param.defaultVal)
- ? param.defaultVal
- : [
- param?.properties?.[0]?.defaultVal,
- param?.properties?.[1]?.defaultVal,
- ];
- }
- const [value, setValue] = React.useState(param.value || defaultVal);
- const updateValue = (newValue: any) => {
- setValue(newValue);
- return onParamChange && onParamChange(operator.id, paramKey, newValue);
- };
-
- switch (param.type) {
- case "input":
- return (
-
- updateValue(e.target.value)}
- placeholder={`请输入${param.name}`}
- className="w-full"
- />
-
- );
- case "select":
- return (
-
-
- );
- case "radio":
- return (
-
- updateValue(e.target.value)}
- >
- {(param.options || []).map((option: any) => (
-
- {typeof option === "string" ? option : option.label}
-
- ))}
-
-
- );
- case "checkbox":
- return (
-
-
-
- );
- case "slider":
- return (
-
-
-
-
-
-
- );
- case "range": {
- const min = param.min || param?.properties?.[0]?.min || 0;
- const max = param.max || param?.properties?.[0]?.max || 1;
- const step = param.step || param?.properties?.[0]?.step || 0.1;
- return (
-
-
- updateValue(Array.isArray(val) ? val : [val, val])
- }
- range
- min={min}
- max={max }
- step={step}
- className="w-full"
- />
-
- updateValue([val1, value[1]])}
- changeOnWheel
- />
- ~
- updateValue([value[0], val2])}
- changeOnWheel
- />
-
-
- );
- }
- case "inputNumber":
- return (
-
- updateValue(val)}
- placeholder={`请输入${param.name}`}
- className="w-full"
- min={param.min}
- max={param.max}
- step={param.step || 1}
- />
-
- );
-
- case "switch":
- return (
-
- updateValue(e.target.checked)}
- >
- {param.name}
-
-
- );
- case "multiple":
- return (
-
- {param.properties.map((subParam) => (
-
- ))}
-
- );
- default:
- return null;
- }
-};
-
-export default ParamConfig;
+import React from "react";
+import {
+ Input,
+ Select,
+ Radio,
+ Checkbox,
+ Form,
+ InputNumber,
+ Slider,
+ Space,
+} from "antd";
+import { ConfigI, OperatorI } from "@/pages/OperatorMarket/operator.model";
+
+interface ParamConfigProps {
+ operator: OperatorI;
+ paramKey: string;
+ param: ConfigI;
+ onParamChange?: (operatorId: string, paramKey: string, value: any) => void;
+}
+
+const ParamConfig: React.FC = ({
+ operator,
+ paramKey,
+ param,
+ onParamChange,
+}) => {
+ if (!param) return null;
+ let defaultVal: any = param.defaultVal;
+ if (param.type === "range") {
+
+ defaultVal = Array.isArray(param.defaultVal)
+ ? param.defaultVal
+ : [
+ param?.properties?.[0]?.defaultVal,
+ param?.properties?.[1]?.defaultVal,
+ ];
+ }
+ const [value, setValue] = React.useState(param.value || defaultVal);
+ const updateValue = (newValue: any) => {
+ setValue(newValue);
+ return onParamChange && onParamChange(operator.id, paramKey, newValue);
+ };
+
+ switch (param.type) {
+ case "input":
+ return (
+
+ updateValue(e.target.value)}
+ placeholder={`请输入${param.name}`}
+ className="w-full"
+ />
+
+ );
+ case "select":
+ return (
+
+
+ );
+ case "radio":
+ return (
+
+ updateValue(e.target.value)}
+ >
+ {(param.options || []).map((option: any) => (
+
+ {typeof option === "string" ? option : option.label}
+
+ ))}
+
+
+ );
+ case "checkbox":
+ return (
+
+
+
+ );
+ case "slider":
+ return (
+
+
+
+
+
+
+ );
+ case "range": {
+ const min = param.min || param?.properties?.[0]?.min || 0;
+ const max = param.max || param?.properties?.[0]?.max || 1;
+ const step = param.step || param?.properties?.[0]?.step || 0.1;
+ return (
+
+
+ updateValue(Array.isArray(val) ? val : [val, val])
+ }
+ range
+ min={min}
+ max={max }
+ step={step}
+ className="w-full"
+ />
+
+ updateValue([val1, value[1]])}
+ changeOnWheel
+ />
+ ~
+ updateValue([value[0], val2])}
+ changeOnWheel
+ />
+
+
+ );
+ }
+ case "inputNumber":
+ return (
+
+ updateValue(val)}
+ placeholder={`请输入${param.name}`}
+ className="w-full"
+ min={param.min}
+ max={param.max}
+ step={param.step || 1}
+ />
+
+ );
+
+ case "switch":
+ return (
+
+ updateValue(e.target.checked)}
+ >
+ {param.name}
+
+
+ );
+ case "multiple":
+ return (
+
+ {param.properties.map((subParam) => (
+
+ ))}
+
+ );
+ default:
+ return null;
+ }
+};
+
+export default ParamConfig;
diff --git a/frontend/src/pages/DataCleansing/Create/hooks/useCreateStepTwo.tsx b/frontend/src/pages/DataCleansing/Create/hooks/useCreateStepTwo.tsx
index 2ce6b612c..cae4d6b98 100644
--- a/frontend/src/pages/DataCleansing/Create/hooks/useCreateStepTwo.tsx
+++ b/frontend/src/pages/DataCleansing/Create/hooks/useCreateStepTwo.tsx
@@ -1,87 +1,87 @@
-import { useDragOperators } from "./useDragOperators";
-import { useOperatorOperations } from "./useOperatorOperations";
-import OperatorConfig from "../components/OperatorConfig";
-import OperatorLibrary from "../components/OperatorLibrary";
-import OperatorOrchestration from "../components/OperatorOrchestration";
-
-export function useCreateStepTwo() {
- const {
- operators,
- selectedOperators,
- templates,
- currentTemplate,
- configOperator,
- currentStep,
- categoryOptions,
- handlePrev,
- handleNext,
- setCurrentTemplate,
- setConfigOperator,
- setSelectedOperators,
- handleConfigChange,
- toggleOperator,
- removeOperator,
- } = useOperatorOperations();
-
- const {
- handleDragStart,
- handleDragEnd,
- handleContainerDragOver,
- handleContainerDragLeave,
- handleItemDragOver,
- handleItemDragLeave,
- handleItemDrop,
- handleDropToContainer,
- } = useDragOperators({
- operators: selectedOperators,
- setOperators: setSelectedOperators,
- });
-
- const renderStepTwo = (
-
- {/* 左侧算子库 */}
-
-
- {/* 中间算子编排区域 */}
-
-
- {/* 右侧参数配置面板 */}
-
-
- );
- return {
- renderStepTwo,
- selectedOperators,
- currentStep,
- handlePrev,
- handleNext,
- };
-}
+import { useDragOperators } from "./useDragOperators";
+import { useOperatorOperations } from "./useOperatorOperations";
+import OperatorConfig from "../components/OperatorConfig";
+import OperatorLibrary from "../components/OperatorLibrary";
+import OperatorOrchestration from "../components/OperatorOrchestration";
+
+export function useCreateStepTwo() {
+ const {
+ operators,
+ selectedOperators,
+ templates,
+ currentTemplate,
+ configOperator,
+ currentStep,
+ categoryOptions,
+ handlePrev,
+ handleNext,
+ setCurrentTemplate,
+ setConfigOperator,
+ setSelectedOperators,
+ handleConfigChange,
+ toggleOperator,
+ removeOperator,
+ } = useOperatorOperations();
+
+ const {
+ handleDragStart,
+ handleDragEnd,
+ handleContainerDragOver,
+ handleContainerDragLeave,
+ handleItemDragOver,
+ handleItemDragLeave,
+ handleItemDrop,
+ handleDropToContainer,
+ } = useDragOperators({
+ operators: selectedOperators,
+ setOperators: setSelectedOperators,
+ });
+
+ const renderStepTwo = (
+
+ {/* 左侧算子库 */}
+
+
+ {/* 中间算子编排区域 */}
+
+
+ {/* 右侧参数配置面板 */}
+
+
+ );
+ return {
+ renderStepTwo,
+ selectedOperators,
+ currentStep,
+ handlePrev,
+ handleNext,
+ };
+}
diff --git a/frontend/src/pages/DataCleansing/Create/hooks/useDragOperators.ts b/frontend/src/pages/DataCleansing/Create/hooks/useDragOperators.ts
index a4d952073..d5b8e90aa 100644
--- a/frontend/src/pages/DataCleansing/Create/hooks/useDragOperators.ts
+++ b/frontend/src/pages/DataCleansing/Create/hooks/useDragOperators.ts
@@ -1,158 +1,158 @@
-import { OperatorI } from "@/pages/OperatorMarket/operator.model";
-import React, { useState } from "react";
-
-export function useDragOperators({
- operators,
- setOperators,
-}: {
- operators: OperatorI[];
- setOperators: (operators: OperatorI[]) => void;
-}) {
- const [draggingItem, setDraggingItem] = useState(null);
- const [draggingSource, setDraggingSource] = useState<
- "library" | "sort" | null
- >(null);
- const [insertPosition, setInsertPosition] = useState<
- "above" | "below" | null
- >(null);
-
- // 处理拖拽开始
- const handleDragStart = (
- e: React.DragEvent,
- item: OperatorI,
- source: "library" | "sort"
- ) => {
- setDraggingItem({
- ...item,
- originalId: item.id,
- });
- setDraggingSource(source);
- e.dataTransfer.effectAllowed = "move";
- };
-
- // 处理拖拽结束
- const handleDragEnd = () => {
- setDraggingItem(null);
- setInsertPosition(null);
- };
-
- // 处理容器拖拽经过
- const handleContainerDragOver = (e: React.DragEvent) => {
- e.preventDefault();
- };
-
- // 处理容器拖拽离开
- const handleContainerDragLeave = (e: React.DragEvent) => {
- if (!e.currentTarget.contains(e.relatedTarget)) {
- setInsertPosition(null);
- }
- };
-
- // 处理项目拖拽经过
- const handleItemDragOver = (e: React.DragEvent) => {
- e.preventDefault();
- e.stopPropagation();
-
- const rect = e.currentTarget.getBoundingClientRect();
- const mouseY = e.clientY;
- const elementMiddle = rect.top + rect.height / 2;
-
- // 判断鼠标在元素的上半部分还是下半部分
- const newPosition = mouseY < elementMiddle ? "above" : "below";
-
- setInsertPosition(newPosition);
- };
-
- // 处理项目拖拽离开
- const handleItemDragLeave = (e: React.DragEvent) => {
- if (!e.currentTarget.contains(e.relatedTarget)) {
- setInsertPosition(null);
- }
- };
-
- // 处理放置到空白区域
- const handleDropToContainer = (e: React.DragEvent) => {
- e.preventDefault();
-
- if (!draggingItem) return;
-
- // 如果是从算子库拖拽过来的
- if (draggingSource === "library") {
- // 检查是否已存在
- const exists = operators.some((item) => item.id === draggingItem.id);
- if (!exists) {
- setOperators([...operators, draggingItem]);
- }
- }
-
- resetDragState();
- };
-
- // 处理放置到特定位置
- const handleItemDrop = (e: React.DragEvent, targetIndex: number) => {
- e.preventDefault();
- e.stopPropagation();
-
- if (!draggingItem) return;
-
- // 从左侧拖拽到右侧的精确插入
- if (draggingSource === "library") {
- if (targetIndex !== -1) {
- const insertIndex =
- insertPosition === "above" ? targetIndex : targetIndex + 1;
-
- // 检查是否已存在
- const exists = operators.some((item) => item.id === draggingItem.id);
- if (!exists) {
- const newRightItems = [...operators];
- newRightItems.splice(insertIndex, 0, draggingItem);
-
- setOperators(newRightItems);
- }
- }
- }
- // 右侧容器内的重新排序
- else if (draggingSource === "sort") {
- const draggedIndex = operators.findIndex(
- (item) => item.id === draggingItem.id
- );
- if (
- draggedIndex !== -1 &&
- targetIndex !== -1 &&
- draggedIndex !== targetIndex
- ) {
- const newItems = [...operators];
- const [draggedItem] = newItems.splice(draggedIndex, 1);
-
- // 计算正确的插入位置
- let insertIndex =
- insertPosition === "above" ? targetIndex : targetIndex + 1;
- if (draggedIndex < insertIndex) {
- insertIndex--; // 调整插入位置,因为已经移除了原元素
- }
-
- newItems.splice(insertIndex, 0, draggedItem);
- setOperators(newItems);
- }
- }
-
- resetDragState();
- };
-
- // 重置拖拽状态
- const resetDragState = () => {
- setDraggingItem(null);
- setInsertPosition(null);
- };
-
- return {
- handleDragStart,
- handleDragEnd,
- handleContainerDragOver,
- handleContainerDragLeave,
- handleItemDragOver,
- handleItemDragLeave,
- handleItemDrop,
- handleDropToContainer,
- };
-}
+import { OperatorI } from "@/pages/OperatorMarket/operator.model";
+import React, { useState } from "react";
+
+export function useDragOperators({
+ operators,
+ setOperators,
+}: {
+ operators: OperatorI[];
+ setOperators: (operators: OperatorI[]) => void;
+}) {
+ const [draggingItem, setDraggingItem] = useState(null);
+ const [draggingSource, setDraggingSource] = useState<
+ "library" | "sort" | null
+ >(null);
+ const [insertPosition, setInsertPosition] = useState<
+ "above" | "below" | null
+ >(null);
+
+ // 处理拖拽开始
+ const handleDragStart = (
+ e: React.DragEvent,
+ item: OperatorI,
+ source: "library" | "sort"
+ ) => {
+ setDraggingItem({
+ ...item,
+ originalId: item.id,
+ });
+ setDraggingSource(source);
+ e.dataTransfer.effectAllowed = "move";
+ };
+
+ // 处理拖拽结束
+ const handleDragEnd = () => {
+ setDraggingItem(null);
+ setInsertPosition(null);
+ };
+
+ // 处理容器拖拽经过
+ const handleContainerDragOver = (e: React.DragEvent) => {
+ e.preventDefault();
+ };
+
+ // 处理容器拖拽离开
+ const handleContainerDragLeave = (e: React.DragEvent) => {
+ if (!e.currentTarget.contains(e.relatedTarget)) {
+ setInsertPosition(null);
+ }
+ };
+
+ // 处理项目拖拽经过
+ const handleItemDragOver = (e: React.DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const rect = e.currentTarget.getBoundingClientRect();
+ const mouseY = e.clientY;
+ const elementMiddle = rect.top + rect.height / 2;
+
+ // 判断鼠标在元素的上半部分还是下半部分
+ const newPosition = mouseY < elementMiddle ? "above" : "below";
+
+ setInsertPosition(newPosition);
+ };
+
+ // 处理项目拖拽离开
+ const handleItemDragLeave = (e: React.DragEvent) => {
+ if (!e.currentTarget.contains(e.relatedTarget)) {
+ setInsertPosition(null);
+ }
+ };
+
+ // 处理放置到空白区域
+ const handleDropToContainer = (e: React.DragEvent) => {
+ e.preventDefault();
+
+ if (!draggingItem) return;
+
+ // 如果是从算子库拖拽过来的
+ if (draggingSource === "library") {
+ // 检查是否已存在
+ const exists = operators.some((item) => item.id === draggingItem.id);
+ if (!exists) {
+ setOperators([...operators, draggingItem]);
+ }
+ }
+
+ resetDragState();
+ };
+
+ // 处理放置到特定位置
+ const handleItemDrop = (e: React.DragEvent, targetIndex: number) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ if (!draggingItem) return;
+
+ // 从左侧拖拽到右侧的精确插入
+ if (draggingSource === "library") {
+ if (targetIndex !== -1) {
+ const insertIndex =
+ insertPosition === "above" ? targetIndex : targetIndex + 1;
+
+ // 检查是否已存在
+ const exists = operators.some((item) => item.id === draggingItem.id);
+ if (!exists) {
+ const newRightItems = [...operators];
+ newRightItems.splice(insertIndex, 0, draggingItem);
+
+ setOperators(newRightItems);
+ }
+ }
+ }
+ // 右侧容器内的重新排序
+ else if (draggingSource === "sort") {
+ const draggedIndex = operators.findIndex(
+ (item) => item.id === draggingItem.id
+ );
+ if (
+ draggedIndex !== -1 &&
+ targetIndex !== -1 &&
+ draggedIndex !== targetIndex
+ ) {
+ const newItems = [...operators];
+ const [draggedItem] = newItems.splice(draggedIndex, 1);
+
+ // 计算正确的插入位置
+ let insertIndex =
+ insertPosition === "above" ? targetIndex : targetIndex + 1;
+ if (draggedIndex < insertIndex) {
+ insertIndex--; // 调整插入位置,因为已经移除了原元素
+ }
+
+ newItems.splice(insertIndex, 0, draggedItem);
+ setOperators(newItems);
+ }
+ }
+
+ resetDragState();
+ };
+
+ // 重置拖拽状态
+ const resetDragState = () => {
+ setDraggingItem(null);
+ setInsertPosition(null);
+ };
+
+ return {
+ handleDragStart,
+ handleDragEnd,
+ handleContainerDragOver,
+ handleContainerDragLeave,
+ handleItemDragOver,
+ handleItemDragLeave,
+ handleItemDrop,
+ handleDropToContainer,
+ };
+}
diff --git a/frontend/src/pages/DataCleansing/Create/hooks/useOperatorOperations.ts b/frontend/src/pages/DataCleansing/Create/hooks/useOperatorOperations.ts
index 604a69bf3..ba257a734 100644
--- a/frontend/src/pages/DataCleansing/Create/hooks/useOperatorOperations.ts
+++ b/frontend/src/pages/DataCleansing/Create/hooks/useOperatorOperations.ts
@@ -1,169 +1,169 @@
-import { useEffect, useState } from "react";
-import { OperatorI } from "@/pages/OperatorMarket/operator.model";
-import { CleansingTemplate } from "../../cleansing.model";
-import {queryCleaningTemplateByIdUsingGet, queryCleaningTemplatesUsingGet} from "../../cleansing.api";
-import {
- queryCategoryTreeUsingGet,
- queryOperatorsUsingPost,
-} from "@/pages/OperatorMarket/operator.api";
-import {useParams} from "react-router";
-
-export function useOperatorOperations() {
- const { id = "" } = useParams();
- const [currentStep, setCurrentStep] = useState(1);
-
- const [operators, setOperators] = useState([]);
- const [selectedOperators, setSelectedOperators] = useState([]);
- const [configOperator, setConfigOperator] = useState(null);
-
- const [templates, setTemplates] = useState([]);
- const [currentTemplate, setCurrentTemplate] =
- useState(null);
-
- // 将后端返回的算子数据映射为前端需要的格式
- const mapOperator = (op: OperatorI) => {
- const configs =
- op.settings
- ? JSON.parse(op.settings)
- : {};
- const defaultParams: Record = {};
- Object.keys(configs).forEach((key) => {
- const { value } = configs[key];
- defaultParams[key] = value;
- });
- return {
- ...op,
- defaultParams,
- configs,
- };
- };
-
- const [categoryOptions, setCategoryOptions] = useState([]);
-
- const initOperators = async () => {
- const [categoryRes, operatorRes] = await Promise.all([
- queryCategoryTreeUsingGet(),
- queryOperatorsUsingPost({ page: 0, size: 1000 }),
- ]);
-
- const operators = operatorRes.data.content.map(mapOperator);
- setOperators(operators || []);
-
- const options = categoryRes.data.content.reduce((acc: any[], item: any) => {
- const cats = item.categories.map((cat) => ({
- ...cat,
- type: item.name,
- label: cat.name,
- value: cat.id,
- icon: cat.icon,
- operators: operators.filter((op) => op[item.name] === cat.name),
- }));
- acc.push(...cats);
- return acc;
- }, [] as { id: string; name: string; icon: React.ReactNode }[]);
-
- setCategoryOptions(options);
- };
-
- const initTemplates = async () => {
- if (id) {
- const { data } = await queryCleaningTemplateByIdUsingGet(id);
- const template = {
- ...data,
- label: data.name,
- value: data.id,
- }
- setTemplates([template])
- setCurrentTemplate(template)
- } else {
- const { data } = await queryCleaningTemplatesUsingGet();
- const newTemplates =
- data.content?.map?.((item) => ({
- ...item,
- label: item.name,
- value: item.id,
- })) || [];
- setTemplates(newTemplates);
- setCurrentTemplate(newTemplates?.[0])
- }
- };
-
- useEffect(() => {
- setSelectedOperators(currentTemplate?.instance?.map(mapOperator) || []);
- }, [currentTemplate]);
-
- useEffect(() => {
- initTemplates();
- initOperators();
- }, []);
-
- const toggleOperator = (operator: OperatorI) => {
- const exist = selectedOperators.find((op) => op.id === operator.id);
- if (exist) {
- setSelectedOperators(
- selectedOperators.filter((op) => op.id !== operator.id)
- );
- } else {
- setSelectedOperators([...selectedOperators, { ...operator }]);
- }
- };
-
- // 删除算子
- const removeOperator = (id: string) => {
- setSelectedOperators(selectedOperators.filter((op) => op.id !== id));
- if (configOperator?.id === id) setConfigOperator(null);
- };
-
- // 配置算子参数变化
- const handleConfigChange = (
- operatorId: string,
- paramKey: string,
- value: any
- ) => {
- setSelectedOperators((prev) =>
- prev.map((op) =>
- op.id === operatorId
- ? {
- ...op,
- overrides: {
- ...(op?.overrides || op?.defaultParams),
- [paramKey]: value,
- },
- }
- : op
- )
- );
- };
-
- const handleNext = () => {
- if (currentStep < 2) {
- setCurrentStep(currentStep + 1);
- }
- };
-
- const handlePrev = () => {
- if (currentStep > 1) {
- setCurrentStep(currentStep - 1);
- }
- };
-
- return {
- currentStep,
- templates,
- currentTemplate,
- configOperator,
- categoryOptions,
- setConfigOperator,
- setCurrentTemplate,
- setCurrentStep,
- operators,
- setOperators,
- selectedOperators,
- setSelectedOperators,
- handleConfigChange,
- toggleOperator,
- removeOperator,
- handleNext,
- handlePrev,
- };
-}
+import { useEffect, useState } from "react";
+import { OperatorI } from "@/pages/OperatorMarket/operator.model";
+import { CleansingTemplate } from "../../cleansing.model";
+import {queryCleaningTemplateByIdUsingGet, queryCleaningTemplatesUsingGet} from "../../cleansing.api";
+import {
+ queryCategoryTreeUsingGet,
+ queryOperatorsUsingPost,
+} from "@/pages/OperatorMarket/operator.api";
+import {useParams} from "react-router";
+
+export function useOperatorOperations() {
+ const { id = "" } = useParams();
+ const [currentStep, setCurrentStep] = useState(1);
+
+ const [operators, setOperators] = useState([]);
+ const [selectedOperators, setSelectedOperators] = useState([]);
+ const [configOperator, setConfigOperator] = useState(null);
+
+ const [templates, setTemplates] = useState([]);
+ const [currentTemplate, setCurrentTemplate] =
+ useState(null);
+
+ // 将后端返回的算子数据映射为前端需要的格式
+ const mapOperator = (op: OperatorI) => {
+ const configs =
+ op.settings
+ ? JSON.parse(op.settings)
+ : {};
+ const defaultParams: Record = {};
+ Object.keys(configs).forEach((key) => {
+ const { value } = configs[key];
+ defaultParams[key] = value;
+ });
+ return {
+ ...op,
+ defaultParams,
+ configs,
+ };
+ };
+
+ const [categoryOptions, setCategoryOptions] = useState([]);
+
+ const initOperators = async () => {
+ const [categoryRes, operatorRes] = await Promise.all([
+ queryCategoryTreeUsingGet(),
+ queryOperatorsUsingPost({ page: 0, size: 1000 }),
+ ]);
+
+ const operators = operatorRes.data.content.map(mapOperator);
+ setOperators(operators || []);
+
+ const options = categoryRes.data.content.reduce((acc: any[], item: any) => {
+ const cats = item.categories.map((cat) => ({
+ ...cat,
+ type: item.name,
+ label: cat.name,
+ value: cat.id,
+ icon: cat.icon,
+ operators: operators.filter((op) => op[item.name] === cat.name),
+ }));
+ acc.push(...cats);
+ return acc;
+ }, [] as { id: string; name: string; icon: React.ReactNode }[]);
+
+ setCategoryOptions(options);
+ };
+
+ const initTemplates = async () => {
+ if (id) {
+ const { data } = await queryCleaningTemplateByIdUsingGet(id);
+ const template = {
+ ...data,
+ label: data.name,
+ value: data.id,
+ }
+ setTemplates([template])
+ setCurrentTemplate(template)
+ } else {
+ const { data } = await queryCleaningTemplatesUsingGet();
+ const newTemplates =
+ data.content?.map?.((item) => ({
+ ...item,
+ label: item.name,
+ value: item.id,
+ })) || [];
+ setTemplates(newTemplates);
+ setCurrentTemplate(newTemplates?.[0])
+ }
+ };
+
+ useEffect(() => {
+ setSelectedOperators(currentTemplate?.instance?.map(mapOperator) || []);
+ }, [currentTemplate]);
+
+ useEffect(() => {
+ initTemplates();
+ initOperators();
+ }, []);
+
+ const toggleOperator = (operator: OperatorI) => {
+ const exist = selectedOperators.find((op) => op.id === operator.id);
+ if (exist) {
+ setSelectedOperators(
+ selectedOperators.filter((op) => op.id !== operator.id)
+ );
+ } else {
+ setSelectedOperators([...selectedOperators, { ...operator }]);
+ }
+ };
+
+ // 删除算子
+ const removeOperator = (id: string) => {
+ setSelectedOperators(selectedOperators.filter((op) => op.id !== id));
+ if (configOperator?.id === id) setConfigOperator(null);
+ };
+
+ // 配置算子参数变化
+ const handleConfigChange = (
+ operatorId: string,
+ paramKey: string,
+ value: any
+ ) => {
+ setSelectedOperators((prev) =>
+ prev.map((op) =>
+ op.id === operatorId
+ ? {
+ ...op,
+ overrides: {
+ ...(op?.overrides || op?.defaultParams),
+ [paramKey]: value,
+ },
+ }
+ : op
+ )
+ );
+ };
+
+ const handleNext = () => {
+ if (currentStep < 2) {
+ setCurrentStep(currentStep + 1);
+ }
+ };
+
+ const handlePrev = () => {
+ if (currentStep > 1) {
+ setCurrentStep(currentStep - 1);
+ }
+ };
+
+ return {
+ currentStep,
+ templates,
+ currentTemplate,
+ configOperator,
+ categoryOptions,
+ setConfigOperator,
+ setCurrentTemplate,
+ setCurrentStep,
+ operators,
+ setOperators,
+ selectedOperators,
+ setSelectedOperators,
+ handleConfigChange,
+ toggleOperator,
+ removeOperator,
+ handleNext,
+ handlePrev,
+ };
+}
diff --git a/frontend/src/pages/DataCleansing/Detail/TaskDetail.tsx b/frontend/src/pages/DataCleansing/Detail/TaskDetail.tsx
index a4cc8b64e..745b701ef 100644
--- a/frontend/src/pages/DataCleansing/Detail/TaskDetail.tsx
+++ b/frontend/src/pages/DataCleansing/Detail/TaskDetail.tsx
@@ -1,223 +1,223 @@
-import { useEffect, useState } from "react";
-import {Breadcrumb, App, Tabs} from "antd";
-import {
- Play,
- Pause,
- Clock,
- CheckCircle,
- AlertCircle,
- Trash2,
- Activity, LayoutList,
-} from "lucide-react";
-import DetailHeader from "@/components/DetailHeader";
-import { Link, useNavigate, useParams } from "react-router";
-import {
- deleteCleaningTaskByIdUsingDelete,
- executeCleaningTaskUsingPost,
- queryCleaningTaskByIdUsingGet, queryCleaningTaskLogByIdUsingGet, queryCleaningTaskResultByIdUsingGet,
- stopCleaningTaskUsingPost,
-} from "../cleansing.api";
-import {mapTask, TaskStatusMap} from "../cleansing.const";
-import {CleansingResult, TaskStatus} from "@/pages/DataCleansing/cleansing.model";
-import BasicInfo from "./components/BasicInfo";
-import OperatorTable from "./components/OperatorTable";
-import FileTable from "./components/FileTable";
-import LogsTable from "./components/LogsTable";
-import {formatExecutionDuration} from "@/utils/unit.ts";
-import {ReloadOutlined} from "@ant-design/icons";
-
-// 任务详情页面组件
-export default function CleansingTaskDetail() {
- const { id = "" } = useParams(); // 获取动态路由参数
- const { message } = App.useApp();
- const navigate = useNavigate();
-
- const fetchTaskDetail = async () => {
- if (!id) return;
- try {
- const { data } = await queryCleaningTaskByIdUsingGet(id);
- setTask(mapTask(data));
- } catch (error) {
- message.error("获取任务详情失败");
- navigate("/data/cleansing");
- }
- };
-
- const pauseTask = async () => {
- await stopCleaningTaskUsingPost(id);
- message.success("任务已暂停");
- fetchTaskDetail();
- };
-
- const startTask = async () => {
- await executeCleaningTaskUsingPost(id);
- message.success("任务已启动");
- fetchTaskDetail();
- };
-
- const deleteTask = async () => {
- await deleteCleaningTaskByIdUsingDelete(id);
- message.success("任务已删除");
- navigate("/data/cleansing");
- };
-
- const [result, setResult] = useState();
-
- const fetchTaskResult = async () => {
- if (!id) return;
- try {
- const { data } = await queryCleaningTaskResultByIdUsingGet(id);
- setResult(data);
- } catch (error) {
- message.error("获取清洗结果失败");
- navigate("/data/cleansing/task-detail/" + id);
- }
- };
-
- const [taskLog, setTaskLog] = useState();
-
- const fetchTaskLog = async () => {
- if (!id) return;
- try {
- const { data } = await queryCleaningTaskLogByIdUsingGet(id);
- setTaskLog(data);
- } catch (error) {
- message.error("获取清洗日志失败");
- navigate("/data/cleansing/task-detail/" + id);
- }
- };
-
- const handleRefresh = async () => {
- fetchTaskDetail();
- {activeTab === "files" && await fetchTaskResult()}
- {activeTab === "logs" && await fetchTaskLog()}
- };
-
- useEffect(() => {
- fetchTaskDetail();
- }, [id]);
-
- const [task, setTask] = useState(null);
- const [activeTab, setActiveTab] = useState("basic");
-
- const headerData = {
- ...task,
- icon: ,
- status: TaskStatusMap[task?.status],
- createdAt: task?.createdAt,
- lastUpdated: task?.updatedAt,
- };
-
- const statistics = [
- {
- icon: ,
- label: "总耗时",
- value: formatExecutionDuration(task?.startedAt, task?.finishedAt) || "--",
- },
- {
- icon: ,
- label: "成功文件",
- value: task?.progress?.succeedFileNum || "0",
- },
- {
- icon: ,
- label: "失败文件",
- value: (task?.status.value === TaskStatus.RUNNING || task?.status.value === TaskStatus.PENDING) ?
- task?.progress.failedFileNum :
- task?.progress?.totalFileNum - task?.progress.succeedFileNum,
- },
- {
- icon: ,
- label: "成功率",
- value: task?.progress?.successRate ? task?.progress?.successRate + "%" : "--",
- },
- ];
-
- const operations = [
- ...(task?.status === TaskStatus.RUNNING
- ? [
- {
- key: "pause",
- label: "暂停任务",
- icon: ,
- onClick: pauseTask,
- },
- ]
- : []),
- ...([TaskStatus.PENDING, TaskStatus.STOPPED, TaskStatus.FAILED].includes(task?.status?.value)
- ? [
- {
- key: "start",
- label: "执行任务",
- icon: ,
- onClick: startTask,
- },
- ]
- : []),
- {
- key: "refresh",
- label: "更新任务",
- icon: ,
- onClick: handleRefresh,
- },
- {
- key: "delete",
- label: "删除任务",
- icon: ,
- danger: true,
- onClick: deleteTask,
- },
- ];
-
- const tabList = [
- {
- key: "basic",
- label: "基本信息",
- },
- {
- key: "operators",
- label: "处理算子",
- },
- {
- key: "files",
- label: "处理文件",
- },
- {
- key: "logs",
- label: "运行日志",
- },
- ];
-
- const breadItems = [
- {
- title: 数据清洗,
- },
- {
- title: "清洗任务详情",
- },
- ];
-
- return (
- <>
-
-
-
-
-
-
-
- {activeTab === "basic" && (
-
- )}
- {activeTab === "operators" && }
- {activeTab === "files" && }
- {activeTab === "logs" && }
-
-
- >
- );
-}
+import { useEffect, useState } from "react";
+import {Breadcrumb, App, Tabs} from "antd";
+import {
+ Play,
+ Pause,
+ Clock,
+ CheckCircle,
+ AlertCircle,
+ Trash2,
+ Activity, LayoutList,
+} from "lucide-react";
+import DetailHeader from "@/components/DetailHeader";
+import { Link, useNavigate, useParams } from "react-router";
+import {
+ deleteCleaningTaskByIdUsingDelete,
+ executeCleaningTaskUsingPost,
+ queryCleaningTaskByIdUsingGet, queryCleaningTaskLogByIdUsingGet, queryCleaningTaskResultByIdUsingGet,
+ stopCleaningTaskUsingPost,
+} from "../cleansing.api";
+import {mapTask, TaskStatusMap} from "../cleansing.const";
+import {CleansingResult, TaskStatus} from "@/pages/DataCleansing/cleansing.model";
+import BasicInfo from "./components/BasicInfo";
+import OperatorTable from "./components/OperatorTable";
+import FileTable from "./components/FileTable";
+import LogsTable from "./components/LogsTable";
+import {formatExecutionDuration} from "@/utils/unit.ts";
+import {ReloadOutlined} from "@ant-design/icons";
+
+// 任务详情页面组件
+export default function CleansingTaskDetail() {
+ const { id = "" } = useParams(); // 获取动态路由参数
+ const { message } = App.useApp();
+ const navigate = useNavigate();
+
+ const fetchTaskDetail = async () => {
+ if (!id) return;
+ try {
+ const { data } = await queryCleaningTaskByIdUsingGet(id);
+ setTask(mapTask(data));
+ } catch (error) {
+ message.error("获取任务详情失败");
+ navigate("/data/cleansing");
+ }
+ };
+
+ const pauseTask = async () => {
+ await stopCleaningTaskUsingPost(id);
+ message.success("任务已暂停");
+ fetchTaskDetail();
+ };
+
+ const startTask = async () => {
+ await executeCleaningTaskUsingPost(id);
+ message.success("任务已启动");
+ fetchTaskDetail();
+ };
+
+ const deleteTask = async () => {
+ await deleteCleaningTaskByIdUsingDelete(id);
+ message.success("任务已删除");
+ navigate("/data/cleansing");
+ };
+
+ const [result, setResult] = useState();
+
+ const fetchTaskResult = async () => {
+ if (!id) return;
+ try {
+ const { data } = await queryCleaningTaskResultByIdUsingGet(id);
+ setResult(data);
+ } catch (error) {
+ message.error("获取清洗结果失败");
+ navigate("/data/cleansing/task-detail/" + id);
+ }
+ };
+
+ const [taskLog, setTaskLog] = useState();
+
+ const fetchTaskLog = async () => {
+ if (!id) return;
+ try {
+ const { data } = await queryCleaningTaskLogByIdUsingGet(id);
+ setTaskLog(data);
+ } catch (error) {
+ message.error("获取清洗日志失败");
+ navigate("/data/cleansing/task-detail/" + id);
+ }
+ };
+
+ const handleRefresh = async () => {
+ fetchTaskDetail();
+ {activeTab === "files" && await fetchTaskResult()}
+ {activeTab === "logs" && await fetchTaskLog()}
+ };
+
+ useEffect(() => {
+ fetchTaskDetail();
+ }, [id]);
+
+ const [task, setTask] = useState(null);
+ const [activeTab, setActiveTab] = useState("basic");
+
+ const headerData = {
+ ...task,
+ icon: ,
+ status: TaskStatusMap[task?.status],
+ createdAt: task?.createdAt,
+ lastUpdated: task?.updatedAt,
+ };
+
+ const statistics = [
+ {
+ icon: ,
+ label: "总耗时",
+ value: formatExecutionDuration(task?.startedAt, task?.finishedAt) || "--",
+ },
+ {
+ icon: ,
+ label: "成功文件",
+ value: task?.progress?.succeedFileNum || "0",
+ },
+ {
+ icon: ,
+ label: "失败文件",
+ value: (task?.status.value === TaskStatus.RUNNING || task?.status.value === TaskStatus.PENDING) ?
+ task?.progress.failedFileNum :
+ task?.progress?.totalFileNum - task?.progress.succeedFileNum,
+ },
+ {
+ icon: ,
+ label: "成功率",
+ value: task?.progress?.successRate ? task?.progress?.successRate + "%" : "--",
+ },
+ ];
+
+ const operations = [
+ ...(task?.status === TaskStatus.RUNNING
+ ? [
+ {
+ key: "pause",
+ label: "暂停任务",
+ icon: ,
+ onClick: pauseTask,
+ },
+ ]
+ : []),
+ ...([TaskStatus.PENDING, TaskStatus.STOPPED, TaskStatus.FAILED].includes(task?.status?.value)
+ ? [
+ {
+ key: "start",
+ label: "执行任务",
+ icon: ,
+ onClick: startTask,
+ },
+ ]
+ : []),
+ {
+ key: "refresh",
+ label: "更新任务",
+ icon: ,
+ onClick: handleRefresh,
+ },
+ {
+ key: "delete",
+ label: "删除任务",
+ icon: ,
+ danger: true,
+ onClick: deleteTask,
+ },
+ ];
+
+ const tabList = [
+ {
+ key: "basic",
+ label: "基本信息",
+ },
+ {
+ key: "operators",
+ label: "处理算子",
+ },
+ {
+ key: "files",
+ label: "处理文件",
+ },
+ {
+ key: "logs",
+ label: "运行日志",
+ },
+ ];
+
+ const breadItems = [
+ {
+ title: 数据清洗,
+ },
+ {
+ title: "清洗任务详情",
+ },
+ ];
+
+ return (
+ <>
+
+
+
+
+
+
+
+ {activeTab === "basic" && (
+
+ )}
+ {activeTab === "operators" && }
+ {activeTab === "files" && }
+ {activeTab === "logs" && }
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/DataCleansing/Detail/TemplateDetail.tsx b/frontend/src/pages/DataCleansing/Detail/TemplateDetail.tsx
index bb6e992ea..ae6ae27f5 100644
--- a/frontend/src/pages/DataCleansing/Detail/TemplateDetail.tsx
+++ b/frontend/src/pages/DataCleansing/Detail/TemplateDetail.tsx
@@ -1,122 +1,122 @@
-import { useEffect, useState } from "react";
-import {Breadcrumb, App, Tabs} from "antd";
-import {
- Trash2,
- LayoutList,
-} from "lucide-react";
-import DetailHeader from "@/components/DetailHeader";
-import { Link, useNavigate, useParams } from "react-router";
-import {
- deleteCleaningTemplateByIdUsingDelete,
- queryCleaningTemplateByIdUsingGet,
-} from "../cleansing.api";
-import {mapTemplate} from "../cleansing.const";
-import OperatorTable from "./components/OperatorTable";
-import {EditOutlined, ReloadOutlined, NumberOutlined} from "@ant-design/icons";
-
-// 任务详情页面组件
-export default function CleansingTemplateDetail() {
- const { id = "" } = useParams(); // 获取动态路由参数
- const { message } = App.useApp();
- const navigate = useNavigate();
- const [template, setTemplate] = useState();
-
- const fetchTemplateDetail = async () => {
- if (!id) return;
- try {
- const { data } = await queryCleaningTemplateByIdUsingGet(id);
- setTemplate(mapTemplate(data));
- } catch (error) {
- message.error("获取任务详情失败");
- navigate("/data/cleansing");
- }
- };
-
- const deleteTemplate = async () => {
- await deleteCleaningTemplateByIdUsingDelete(id);
- message.success("模板已删除");
- navigate("/data/cleansing");
- };
-
- const handleRefresh = async () => {
- fetchTemplateDetail();
- };
-
- useEffect(() => {
- fetchTemplateDetail();
- }, [id]);
-
- const [activeTab, setActiveTab] = useState("operators");
-
- const headerData = {
- ...template,
- icon: ,
- createdAt: template?.createdAt,
- lastUpdated: template?.updatedAt,
- };
-
- const statistics = [
- {
- icon: ,
- label: "算子数量",
- value: template?.instance?.length || 0,
- },
- ];
-
- const operations = [
- {
- key: "update",
- label: "更新任务",
- icon: ,
- onClick: () => navigate(`/data/cleansing/update-template/${id}`),
- },
- {
- key: "refresh",
- label: "更新任务",
- icon: ,
- onClick: handleRefresh,
- },
- {
- key: "delete",
- label: "删除任务",
- icon: ,
- danger: true,
- onClick: deleteTemplate,
- },
- ];
-
- const tabList = [
- {
- key: "operators",
- label: "处理算子",
- },
- ];
-
- const breadItems = [
- {
- title: 数据清洗,
- },
- {
- title: "模板详情",
- },
- ];
-
- return (
- <>
-
-
-
-
-
- >
- );
-}
+import { useEffect, useState } from "react";
+import {Breadcrumb, App, Tabs} from "antd";
+import {
+ Trash2,
+ LayoutList,
+} from "lucide-react";
+import DetailHeader from "@/components/DetailHeader";
+import { Link, useNavigate, useParams } from "react-router";
+import {
+ deleteCleaningTemplateByIdUsingDelete,
+ queryCleaningTemplateByIdUsingGet,
+} from "../cleansing.api";
+import {mapTemplate} from "../cleansing.const";
+import OperatorTable from "./components/OperatorTable";
+import {EditOutlined, ReloadOutlined, NumberOutlined} from "@ant-design/icons";
+
+// 任务详情页面组件
+export default function CleansingTemplateDetail() {
+ const { id = "" } = useParams(); // 获取动态路由参数
+ const { message } = App.useApp();
+ const navigate = useNavigate();
+ const [template, setTemplate] = useState();
+
+ const fetchTemplateDetail = async () => {
+ if (!id) return;
+ try {
+ const { data } = await queryCleaningTemplateByIdUsingGet(id);
+ setTemplate(mapTemplate(data));
+ } catch (error) {
+ message.error("获取任务详情失败");
+ navigate("/data/cleansing");
+ }
+ };
+
+ const deleteTemplate = async () => {
+ await deleteCleaningTemplateByIdUsingDelete(id);
+ message.success("模板已删除");
+ navigate("/data/cleansing");
+ };
+
+ const handleRefresh = async () => {
+ fetchTemplateDetail();
+ };
+
+ useEffect(() => {
+ fetchTemplateDetail();
+ }, [id]);
+
+ const [activeTab, setActiveTab] = useState("operators");
+
+ const headerData = {
+ ...template,
+ icon: ,
+ createdAt: template?.createdAt,
+ lastUpdated: template?.updatedAt,
+ };
+
+ const statistics = [
+ {
+ icon: ,
+ label: "算子数量",
+ value: template?.instance?.length || 0,
+ },
+ ];
+
+ const operations = [
+ {
+ key: "update",
+ label: "更新任务",
+ icon: ,
+ onClick: () => navigate(`/data/cleansing/update-template/${id}`),
+ },
+ {
+ key: "refresh",
+ label: "更新任务",
+ icon: ,
+ onClick: handleRefresh,
+ },
+ {
+ key: "delete",
+ label: "删除任务",
+ icon: ,
+ danger: true,
+ onClick: deleteTemplate,
+ },
+ ];
+
+ const tabList = [
+ {
+ key: "operators",
+ label: "处理算子",
+ },
+ ];
+
+ const breadItems = [
+ {
+ title: 数据清洗,
+ },
+ {
+ title: "模板详情",
+ },
+ ];
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/DataCleansing/Detail/components/BasicInfo.tsx b/frontend/src/pages/DataCleansing/Detail/components/BasicInfo.tsx
index 78f2c2a4b..cfa257250 100644
--- a/frontend/src/pages/DataCleansing/Detail/components/BasicInfo.tsx
+++ b/frontend/src/pages/DataCleansing/Detail/components/BasicInfo.tsx
@@ -1,138 +1,138 @@
-import {CleansingTask, TaskStatus} from "@/pages/DataCleansing/cleansing.model";
-import { Button, Card, Descriptions, Progress } from "antd";
-import { Activity, AlertCircle, CheckCircle, Clock } from "lucide-react";
-import { useNavigate } from "react-router";
-import {formatExecutionDuration} from "@/utils/unit.ts";
-
-export default function BasicInfo({ task }: { task: CleansingTask }) {
- const navigate = useNavigate();
-
- const descriptionItems = [
- {
- key: "id",
- label: "任务ID",
- children: {task?.id},
- },
- { key: "name", label: "任务名称", children: task?.name },
- {
- key: "dataset",
- label: "源数据集",
- children: (
-
- navigate("/data/management/detail/" + task?.srcDatasetId)
- }
- >
- {task?.srcDatasetName}
-
- ),
- },
- {
- key: "targetDataset",
- label: "目标数据集",
- children: (
-
- navigate("/data/management/detail/" + task?.destDatasetId)
- }
- >
- {task?.destDatasetName}
-
- ),
- },
- { key: "startTime", label: "开始时间", children: task?.startedAt },
- {
- key: "description",
- label: "任务描述",
- children: (
- {task?.description || "--"}
- ),
- span: 2,
- },
- ];
-
- return (
- <>
- {/* 执行摘要 */}
-
-
-
-
-
- {formatExecutionDuration(task?.startedAt, task?.finishedAt) || "--"}
-
-
总耗时
-
-
-
-
- {task?.progress?.succeedFileNum || "0"}
-
-
成功文件
-
-
-
-
- {(task?.status.value === TaskStatus.RUNNING || task?.status.value === TaskStatus.PENDING) ?
- task?.progress.failedFileNum :
- task?.progress?.totalFileNum - task?.progress.succeedFileNum}
-
-
失败文件
-
-
-
-
- {task?.progress?.successRate ? task?.progress?.successRate + "%" : "--"}
-
-
成功率
-
-
-
- {/* 基本信息 */}
-
-
-
基本信息
-
-
- {/* 处理进度 */}
-
-
处理进度
- { task?.status?.value === TaskStatus.FAILED ?
-
- :
- }
-
-
-
- 已完成: {task?.progress?.succeedFileNum || "0"}
-
-
-
- 处理中: {(task?.status.value === TaskStatus.RUNNING || task?.status.value === TaskStatus.PENDING) ?
- task?.progress?.totalFileNum - task?.progress.succeedFileNum : 0}
-
-
-
- 失败: {(task?.status.value === TaskStatus.RUNNING || task?.status.value === TaskStatus.PENDING) ?
- task?.progress.failedFileNum :
- task?.progress?.totalFileNum - task?.progress.succeedFileNum}
-
-
-
-
- >
- );
-}
+import {CleansingTask, TaskStatus} from "@/pages/DataCleansing/cleansing.model";
+import { Button, Card, Descriptions, Progress } from "antd";
+import { Activity, AlertCircle, CheckCircle, Clock } from "lucide-react";
+import { useNavigate } from "react-router";
+import {formatExecutionDuration} from "@/utils/unit.ts";
+
+export default function BasicInfo({ task }: { task: CleansingTask }) {
+ const navigate = useNavigate();
+
+ const descriptionItems = [
+ {
+ key: "id",
+ label: "任务ID",
+ children: {task?.id},
+ },
+ { key: "name", label: "任务名称", children: task?.name },
+ {
+ key: "dataset",
+ label: "源数据集",
+ children: (
+
+ navigate("/data/management/detail/" + task?.srcDatasetId)
+ }
+ >
+ {task?.srcDatasetName}
+
+ ),
+ },
+ {
+ key: "targetDataset",
+ label: "目标数据集",
+ children: (
+
+ navigate("/data/management/detail/" + task?.destDatasetId)
+ }
+ >
+ {task?.destDatasetName}
+
+ ),
+ },
+ { key: "startTime", label: "开始时间", children: task?.startedAt },
+ {
+ key: "description",
+ label: "任务描述",
+ children: (
+ {task?.description || "--"}
+ ),
+ span: 2,
+ },
+ ];
+
+ return (
+ <>
+ {/* 执行摘要 */}
+
+
+
+
+
+ {formatExecutionDuration(task?.startedAt, task?.finishedAt) || "--"}
+
+
总耗时
+
+
+
+
+ {task?.progress?.succeedFileNum || "0"}
+
+
成功文件
+
+
+
+
+ {(task?.status.value === TaskStatus.RUNNING || task?.status.value === TaskStatus.PENDING) ?
+ task?.progress.failedFileNum :
+ task?.progress?.totalFileNum - task?.progress.succeedFileNum}
+
+
失败文件
+
+
+
+
+ {task?.progress?.successRate ? task?.progress?.successRate + "%" : "--"}
+
+
成功率
+
+
+
+ {/* 基本信息 */}
+
+
+
基本信息
+
+
+ {/* 处理进度 */}
+
+
处理进度
+ { task?.status?.value === TaskStatus.FAILED ?
+
+ :
+ }
+
+
+
+ 已完成: {task?.progress?.succeedFileNum || "0"}
+
+
+
+ 处理中: {(task?.status.value === TaskStatus.RUNNING || task?.status.value === TaskStatus.PENDING) ?
+ task?.progress?.totalFileNum - task?.progress.succeedFileNum : 0}
+
+
+
+ 失败: {(task?.status.value === TaskStatus.RUNNING || task?.status.value === TaskStatus.PENDING) ?
+ task?.progress.failedFileNum :
+ task?.progress?.totalFileNum - task?.progress.succeedFileNum}
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/DataCleansing/Detail/components/FileTable.tsx b/frontend/src/pages/DataCleansing/Detail/components/FileTable.tsx
index f1e422498..f40f40c7c 100644
--- a/frontend/src/pages/DataCleansing/Detail/components/FileTable.tsx
+++ b/frontend/src/pages/DataCleansing/Detail/components/FileTable.tsx
@@ -1,397 +1,397 @@
-import {Button, Modal, Table, Badge, Input, Popover} from "antd";
-import { Download } from "lucide-react";
-import {useEffect, useState} from "react";
-import {useParams} from "react-router";
-import {TaskStatus} from "@/pages/DataCleansing/cleansing.model.ts";
-import {TaskStatusMap} from "@/pages/DataCleansing/cleansing.const.tsx";
-
-// 模拟文件列表数据
-export default function FileTable({result, fetchTaskResult}) {
- const { id = "" } = useParams();
- const [showFileCompareDialog, setShowFileCompareDialog] = useState(false);
- const [selectedFile, setSelectedFile] = useState(null);
- const [selectedFileIds, setSelectedFileIds] = useState([]);
-
- useEffect(() => {
- fetchTaskResult();
- }, [id]);
-
- const handleSelectAllFiles = (checked: boolean) => {
- if (checked) {
- setSelectedFileIds(result.map((file) => file.instanceId));
- } else {
- setSelectedFileIds([]);
- }
- };
-
- const handleSelectFile = (fileId: string, checked: boolean) => {
- if (checked) {
- setSelectedFileIds([...selectedFileIds, fileId]);
- } else {
- setSelectedFileIds(selectedFileIds.filter((id) => id !== fileId));
- }
- };
- const handleViewFileCompare = (file: any) => {
- setSelectedFile(file);
- setShowFileCompareDialog(true);
- };
- const handleBatchDownload = () => {
- // 实际下载逻辑
- };
-
- function formatFileSize(bytes: number, decimals: number = 2): string {
- if (bytes === 0) return '0 Bytes';
-
- const k = 1024;
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
-
- const i = Math.floor(Math.log(bytes) / Math.log(k));
-
- return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
- }
-
- const fileColumns = [
- {
- title: (
- 0
- }
- onChange={(e) => handleSelectAllFiles(e.target.checked)}
- className="w-4 h-4"
- />
- ),
- dataIndex: "select",
- key: "select",
- width: 50,
- render: (_text: string, record: any) => (
- handleSelectFile(record.id, e.target.checked)}
- className="w-4 h-4"
- />
- ),
- },
- {
- title: "文件名",
- dataIndex: "srcName",
- key: "srcName",
- width: 200,
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- }: any) => (
-
-
- setSelectedKeys(e.target.value ? [e.target.value] : [])
- }
- onPressEnter={() => confirm()}
- className="mb-2"
- />
-
- confirm()}>
- 搜索
-
- clearFilters()}>
- 重置
-
-
-
- ),
- onFilter: (value: string, record: any) =>
- record.srcName.toLowerCase().includes(value.toLowerCase()),
- render: (text: string) => (
- {text?.replace(/\.[^/.]+$/, "")}
- ),
- },
- {
- title: "清洗后文件名",
- dataIndex: "destName",
- key: "destName",
- width: 200,
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- }: any) => (
-
-
- setSelectedKeys(e.target.value ? [e.target.value] : [])
- }
- onPressEnter={() => confirm()}
- className="mb-2"
- />
-
- confirm()}>
- 搜索
-
- clearFilters()}>
- 重置
-
-
-
- ),
- onFilter: (value: string, record: any) =>
- record.destName.toLowerCase().includes(value.toLowerCase()),
- render: (text: string) => (
- {text?.replace(/\.[^/.]+$/, "")}
- ),
- },
- {
- title: "文件类型",
- dataIndex: "srcType",
- key: "srcType",
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- }: any) => (
-
-
- setSelectedKeys(e.target.value ? [e.target.value] : [])
- }
- onPressEnter={() => confirm()}
- className="mb-2"
- />
-
- confirm()}>
- 搜索
-
- clearFilters()}>
- 重置
-
-
-
- ),
- onFilter: (value: string, record: any) =>
- record.srcType.toLowerCase().includes(value.toLowerCase()),
- render: (text: string) => (
- {text}
- ),
- },
- {
- title: "清洗后文件类型",
- dataIndex: "destType",
- key: "destType",
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- }: any) => (
-
-
- setSelectedKeys(e.target.value ? [e.target.value] : [])
- }
- onPressEnter={() => confirm()}
- className="mb-2"
- />
-
- confirm()}>
- 搜索
-
- clearFilters()}>
- 重置
-
-
-
- ),
- onFilter: (value: string, record: any) =>
- record.destType.toLowerCase().includes(value.toLowerCase()),
- render: (text: string) => (
- {text}
- ),
- },
- {
- title: "清洗前大小",
- dataIndex: "srcSize",
- key: "srcSize",
- sorter: (a: any, b: any) => {
- const getSizeInBytes = (size: string) => {
- if (!size || size === "-") return 0;
- const num = Number.parseFloat(size);
- if (size.includes("GB")) return num * 1024 * 1024 * 1024;
- if (size.includes("MB")) return num * 1024 * 1024;
- if (size.includes("KB")) return num * 1024;
- return num;
- };
- return getSizeInBytes(a.originalSize) - getSizeInBytes(b.originalSize);
- },
- render: (number: number) => (
- {formatFileSize(number)}
- ),
- },
- {
- title: "清洗后大小",
- dataIndex: "destSize",
- key: "destSize",
- sorter: (a: any, b: any) => {
- const getSizeInBytes = (size: string) => {
- if (!size || size === "-") return 0;
- const num = Number.parseFloat(size);
- if (size.includes("GB")) return num * 1024 * 1024 * 1024;
- if (size.includes("MB")) return num * 1024 * 1024;
- if (size.includes("KB")) return num * 1024;
- return num;
- };
- return (
- getSizeInBytes(a.processedSize) - getSizeInBytes(b.processedSize)
- );
- },
- render: (number: number) => (
- {formatFileSize(number)}
- ),
- },
- {
- title: "状态",
- dataIndex: "status",
- key: "status",
- filters: [
- { text: "已完成", value: "COMPLETED" },
- { text: "失败", value: "FAILED" },
- ],
- onFilter: (value: string, record: any) => record.status === value,
- render: (status: string) => (
-
- ),
- },
- {
- title: "操作",
- key: "action",
- width: 200,
- render: (_text: string, record: any) => (
-
- {record.status === "COMPLETED" ? (
-
handleViewFileCompare(record)}
- >
- 对比
-
- ) : (
-
- 对比
-
- )}
-
- 下载
-
-
- ),
- },
- ];
-
- return (
- <>
- {selectedFileIds.length > 0 && (
-
-
-
- 已选择 {selectedFileIds.length} 个文件
-
- }
- >
- 批量下载
-
-
-
- )}
-
-
- {/* 文件对比弹窗 */}
- setShowFileCompareDialog(false)}
- footer={null}
- width={900}
- title={文件对比 - {selectedFile?.fileName}}
- >
-
-
-
清洗前
-
-
-
-
原始文件预览
-
- 大小: {formatFileSize(selectedFile?.srcSize)}
-
-
-
-
-
- 文件格式: {selectedFile?.srcType}
-
-
-
-
-
清洗后
-
-
-
-
处理后文件预览
-
- 大小: {formatFileSize(selectedFile?.destSize)}
-
-
-
-
-
- 文件格式: {selectedFile?.destType}
-
-
-
-
-
-
处理效果对比
-
-
-
文件大小优化
-
减少了 {(100 * (selectedFile?.srcSize - selectedFile?.destSize) / selectedFile?.srcSize).toFixed(2)}%
-
-
-
-
- >
- );
-}
+import {Button, Modal, Table, Badge, Input, Popover} from "antd";
+import { Download } from "lucide-react";
+import {useEffect, useState} from "react";
+import {useParams} from "react-router";
+import {TaskStatus} from "@/pages/DataCleansing/cleansing.model.ts";
+import {TaskStatusMap} from "@/pages/DataCleansing/cleansing.const.tsx";
+
+// 模拟文件列表数据
+export default function FileTable({result, fetchTaskResult}) {
+ const { id = "" } = useParams();
+ const [showFileCompareDialog, setShowFileCompareDialog] = useState(false);
+ const [selectedFile, setSelectedFile] = useState(null);
+ const [selectedFileIds, setSelectedFileIds] = useState([]);
+
+ useEffect(() => {
+ fetchTaskResult();
+ }, [id]);
+
+ const handleSelectAllFiles = (checked: boolean) => {
+ if (checked) {
+ setSelectedFileIds(result.map((file) => file.instanceId));
+ } else {
+ setSelectedFileIds([]);
+ }
+ };
+
+ const handleSelectFile = (fileId: string, checked: boolean) => {
+ if (checked) {
+ setSelectedFileIds([...selectedFileIds, fileId]);
+ } else {
+ setSelectedFileIds(selectedFileIds.filter((id) => id !== fileId));
+ }
+ };
+ const handleViewFileCompare = (file: any) => {
+ setSelectedFile(file);
+ setShowFileCompareDialog(true);
+ };
+ const handleBatchDownload = () => {
+ // 实际下载逻辑
+ };
+
+ function formatFileSize(bytes: number, decimals: number = 2): string {
+ if (bytes === 0) return '0 Bytes';
+
+ const k = 1024;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
+ }
+
+ const fileColumns = [
+ {
+ title: (
+ 0
+ }
+ onChange={(e) => handleSelectAllFiles(e.target.checked)}
+ className="w-4 h-4"
+ />
+ ),
+ dataIndex: "select",
+ key: "select",
+ width: 50,
+ render: (_text: string, record: any) => (
+ handleSelectFile(record.id, e.target.checked)}
+ className="w-4 h-4"
+ />
+ ),
+ },
+ {
+ title: "文件名",
+ dataIndex: "srcName",
+ key: "srcName",
+ width: 200,
+ filterDropdown: ({
+ setSelectedKeys,
+ selectedKeys,
+ confirm,
+ clearFilters,
+ }: any) => (
+
+
+ setSelectedKeys(e.target.value ? [e.target.value] : [])
+ }
+ onPressEnter={() => confirm()}
+ className="mb-2"
+ />
+
+ confirm()}>
+ 搜索
+
+ clearFilters()}>
+ 重置
+
+
+
+ ),
+ onFilter: (value: string, record: any) =>
+ record.srcName.toLowerCase().includes(value.toLowerCase()),
+ render: (text: string) => (
+ {text?.replace(/\.[^/.]+$/, "")}
+ ),
+ },
+ {
+ title: "清洗后文件名",
+ dataIndex: "destName",
+ key: "destName",
+ width: 200,
+ filterDropdown: ({
+ setSelectedKeys,
+ selectedKeys,
+ confirm,
+ clearFilters,
+ }: any) => (
+
+
+ setSelectedKeys(e.target.value ? [e.target.value] : [])
+ }
+ onPressEnter={() => confirm()}
+ className="mb-2"
+ />
+
+ confirm()}>
+ 搜索
+
+ clearFilters()}>
+ 重置
+
+
+
+ ),
+ onFilter: (value: string, record: any) =>
+ record.destName.toLowerCase().includes(value.toLowerCase()),
+ render: (text: string) => (
+ {text?.replace(/\.[^/.]+$/, "")}
+ ),
+ },
+ {
+ title: "文件类型",
+ dataIndex: "srcType",
+ key: "srcType",
+ filterDropdown: ({
+ setSelectedKeys,
+ selectedKeys,
+ confirm,
+ clearFilters,
+ }: any) => (
+
+
+ setSelectedKeys(e.target.value ? [e.target.value] : [])
+ }
+ onPressEnter={() => confirm()}
+ className="mb-2"
+ />
+
+ confirm()}>
+ 搜索
+
+ clearFilters()}>
+ 重置
+
+
+
+ ),
+ onFilter: (value: string, record: any) =>
+ record.srcType.toLowerCase().includes(value.toLowerCase()),
+ render: (text: string) => (
+ {text}
+ ),
+ },
+ {
+ title: "清洗后文件类型",
+ dataIndex: "destType",
+ key: "destType",
+ filterDropdown: ({
+ setSelectedKeys,
+ selectedKeys,
+ confirm,
+ clearFilters,
+ }: any) => (
+
+
+ setSelectedKeys(e.target.value ? [e.target.value] : [])
+ }
+ onPressEnter={() => confirm()}
+ className="mb-2"
+ />
+
+ confirm()}>
+ 搜索
+
+ clearFilters()}>
+ 重置
+
+
+
+ ),
+ onFilter: (value: string, record: any) =>
+ record.destType.toLowerCase().includes(value.toLowerCase()),
+ render: (text: string) => (
+ {text}
+ ),
+ },
+ {
+ title: "清洗前大小",
+ dataIndex: "srcSize",
+ key: "srcSize",
+ sorter: (a: any, b: any) => {
+ const getSizeInBytes = (size: string) => {
+ if (!size || size === "-") return 0;
+ const num = Number.parseFloat(size);
+ if (size.includes("GB")) return num * 1024 * 1024 * 1024;
+ if (size.includes("MB")) return num * 1024 * 1024;
+ if (size.includes("KB")) return num * 1024;
+ return num;
+ };
+ return getSizeInBytes(a.originalSize) - getSizeInBytes(b.originalSize);
+ },
+ render: (number: number) => (
+ {formatFileSize(number)}
+ ),
+ },
+ {
+ title: "清洗后大小",
+ dataIndex: "destSize",
+ key: "destSize",
+ sorter: (a: any, b: any) => {
+ const getSizeInBytes = (size: string) => {
+ if (!size || size === "-") return 0;
+ const num = Number.parseFloat(size);
+ if (size.includes("GB")) return num * 1024 * 1024 * 1024;
+ if (size.includes("MB")) return num * 1024 * 1024;
+ if (size.includes("KB")) return num * 1024;
+ return num;
+ };
+ return (
+ getSizeInBytes(a.processedSize) - getSizeInBytes(b.processedSize)
+ );
+ },
+ render: (number: number) => (
+ {formatFileSize(number)}
+ ),
+ },
+ {
+ title: "状态",
+ dataIndex: "status",
+ key: "status",
+ filters: [
+ { text: "已完成", value: "COMPLETED" },
+ { text: "失败", value: "FAILED" },
+ ],
+ onFilter: (value: string, record: any) => record.status === value,
+ render: (status: string) => (
+
+ ),
+ },
+ {
+ title: "操作",
+ key: "action",
+ width: 200,
+ render: (_text: string, record: any) => (
+
+ {record.status === "COMPLETED" ? (
+
handleViewFileCompare(record)}
+ >
+ 对比
+
+ ) : (
+
+ 对比
+
+ )}
+
+ 下载
+
+
+ ),
+ },
+ ];
+
+ return (
+ <>
+ {selectedFileIds.length > 0 && (
+
+
+
+ 已选择 {selectedFileIds.length} 个文件
+
+ }
+ >
+ 批量下载
+
+
+
+ )}
+
+
+ {/* 文件对比弹窗 */}
+ setShowFileCompareDialog(false)}
+ footer={null}
+ width={900}
+ title={文件对比 - {selectedFile?.fileName}}
+ >
+
+
+
清洗前
+
+
+
+
原始文件预览
+
+ 大小: {formatFileSize(selectedFile?.srcSize)}
+
+
+
+
+
+ 文件格式: {selectedFile?.srcType}
+
+
+
+
+
清洗后
+
+
+
+
处理后文件预览
+
+ 大小: {formatFileSize(selectedFile?.destSize)}
+
+
+
+
+
+ 文件格式: {selectedFile?.destType}
+
+
+
+
+
+
处理效果对比
+
+
+
文件大小优化
+
减少了 {(100 * (selectedFile?.srcSize - selectedFile?.destSize) / selectedFile?.srcSize).toFixed(2)}%
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/DataCleansing/Detail/components/LogsTable.tsx b/frontend/src/pages/DataCleansing/Detail/components/LogsTable.tsx
index 6a56064da..4e9376af6 100644
--- a/frontend/src/pages/DataCleansing/Detail/components/LogsTable.tsx
+++ b/frontend/src/pages/DataCleansing/Detail/components/LogsTable.tsx
@@ -1,43 +1,43 @@
-import {useEffect} from "react";
-import {useParams} from "react-router";
-import {FileClock} from "lucide-react";
-
-export default function LogsTable({taskLog, fetchTaskLog} : {taskLog: any[], fetchTaskLog: () => Promise}) {
- const { id = "" } = useParams();
-
- useEffect(() => {
- fetchTaskLog();
- }, [id]);
-
- return taskLog?.length > 0 ? (
- <>
-
-
- {taskLog?.map?.((log, index) => (
-
-
- [{log.level}]
-
- {log.message}
-
- ))}
-
-
- >
- ) : (
-
-
-
- 当前任务无可用日志
-
-
- );
-}
+import {useEffect} from "react";
+import {useParams} from "react-router";
+import {FileClock} from "lucide-react";
+
+export default function LogsTable({taskLog, fetchTaskLog} : {taskLog: any[], fetchTaskLog: () => Promise}) {
+ const { id = "" } = useParams();
+
+ useEffect(() => {
+ fetchTaskLog();
+ }, [id]);
+
+ return taskLog?.length > 0 ? (
+ <>
+
+
+ {taskLog?.map?.((log, index) => (
+
+
+ [{log.level}]
+
+ {log.message}
+
+ ))}
+
+
+ >
+ ) : (
+
+
+
+ 当前任务无可用日志
+
+
+ );
+}
diff --git a/frontend/src/pages/DataCleansing/Detail/components/OperatorTable.tsx b/frontend/src/pages/DataCleansing/Detail/components/OperatorTable.tsx
index 51ca73757..dc556bbac 100644
--- a/frontend/src/pages/DataCleansing/Detail/components/OperatorTable.tsx
+++ b/frontend/src/pages/DataCleansing/Detail/components/OperatorTable.tsx
@@ -1,25 +1,25 @@
-import {Steps, Typography} from "antd";
-import {useNavigate} from "react-router";
-
-export default function OperatorTable({ task }: { task: any }) {
- const navigate = useNavigate();
-
- return task?.instance?.length > 0 && (
- <>
- ({
- title: navigate(`/data/operator-market/plugin-detail/${item?.id}`)}
- >
- {item?.name}
- ,
- description: item?.description,
- status: "finish"
- }))}
- className="overflow-auto"
- />
- >
- );
-}
+import {Steps, Typography} from "antd";
+import {useNavigate} from "react-router";
+
+export default function OperatorTable({ task }: { task: any }) {
+ const navigate = useNavigate();
+
+ return task?.instance?.length > 0 && (
+ <>
+ ({
+ title: navigate(`/data/operator-market/plugin-detail/${item?.id}`)}
+ >
+ {item?.name}
+ ,
+ description: item?.description,
+ status: "finish"
+ }))}
+ className="overflow-auto"
+ />
+ >
+ );
+}
diff --git a/frontend/src/pages/DataCleansing/Home/DataCleansing.tsx b/frontend/src/pages/DataCleansing/Home/DataCleansing.tsx
index 2086ca457..af28c5385 100644
--- a/frontend/src/pages/DataCleansing/Home/DataCleansing.tsx
+++ b/frontend/src/pages/DataCleansing/Home/DataCleansing.tsx
@@ -1,61 +1,61 @@
-import { useEffect, useState } from "react";
-import { Tabs, Button } from "antd";
-import { PlusOutlined } from "@ant-design/icons";
-import { useNavigate } from "react-router";
-import TaskList from "./components/TaskList";
-import TemplateList from "./components/TemplateList";
-import ProcessFlowDiagram from "./components/ProcessFlowDiagram";
-import { useSearchParams } from "@/hooks/useSearchParams";
-
-export default function DataProcessingPage() {
- const navigate = useNavigate();
- const urlParams = useSearchParams();
- const [currentView, setCurrentView] = useState<"task" | "template">("task");
-
- useEffect(() => {
- if (urlParams.view) {
- setCurrentView(urlParams.view);
- }
- }, [urlParams]);
-
- return (
-
- {/* Header */}
-
-
数据清洗
-
-